Pokud jste vývojář, který jste použili programovací jazyky. Jsou to úžasné způsoby, jak přimět počítač, aby dělal to, co chcete. Možná jste dokonce holubice hluboko a naprogramován v sestavě nebo strojového kódu. Mnozí se nikdy nechtějí vrátit. Ale někteří se diví, jak se mohu více mučit tím, že dělám programování na nižší úrovni? Chci se dozvědět více o tom, jak jsou programovací jazyky vytvořeny!, Všechny srandu stranou, psaní nového jazyka není tak zlé, jak to zní, takže pokud máte i mírnou zvědavost, já bych navrhnout budete držet kolem a vidět, co to je.
tento příspěvek má poskytnout jednoduchý ponor do toho, jak lze vytvořit programovací jazyk a jak si můžete vytvořit svůj vlastní speciální jazyk. Možná to dokonce pojmenujte po sobě. Kdo ví.
také se vsadím, že to vypadá jako neuvěřitelně skličující úkol. Nebojte se, protože jsem to zvážil. Snažil jsem se vysvětlit všechno relativně jednoduše, aniž bych se příliš mnoho tangentů., Na konci tohoto příspěvku budete moci vytvořit svůj vlastní programovací jazyk (bude zde několik částí), ale je toho víc. Vědět, co se děje pod kapotou, vás zlepší při ladění. Budete lépe rozumět novým programovacím jazykům a proč dělají rozhodnutí, která dělají. Můžete mít programovací jazyk pojmenovaný po sobě, pokud jsem to předtím nezmínil. Taky, je to opravdu zábava. Alespoň pro mě.
Překladače a tlumočníci
programovací jazyky jsou obecně na vysoké úrovni. To znamená, že se nedíváte na 0s a 1s, ani na Registry a montážní kód., Ale váš počítač chápe pouze 0s a 1s, takže potřebuje způsob, jak se snadno přesunout z toho, co čtete, na to, co může stroj snadno číst. Tento překlad lze provést kompilací nebo interpretací.
kompilace je proces přeměny celého zdrojového souboru zdrojového jazyka na cílový jazyk. Pro naše účely, budeme přemýšlet o kompilaci z vašeho zbrusu nového, nejmodernější jazyk, celou cestu dolů na runnable strojový kód.,
Mým cílem je, aby se „magické“ zmizí
Interpretace je proces provádění kódu ve zdrojovém souboru více či méně přímo. Nechám tě myslet si, že je to kouzlo.
Jak tedy přejít od snadno čitelného zdrojového jazyka k těžko srozumitelnému cílovému jazyku?
Fáze kompilátoru
kompilátor lze rozdělit do fází různými způsoby, ale existuje jeden způsob, který je nejčastější., To dává jen malé množství smyslu, když to poprvé uvidíte, ale tady to jde:
Jejda, vybral jsem špatný diagram, ale to bude dělat. V podstatě, můžete získat zdrojový soubor, dáte jej do formátu, který počítač chce (odstranění bílého místa a věci, jako je to), změnit jej v něco, co počítač může pohybovat no, a pak generovat kód z tohoto. Je toho víc. To je na jindy, nebo pro svůj vlastní výzkum, pokud vás vaše zvědavost zabíjí.,
Lexikální Analýza
AKA „, Aby zdrojový kód byl docela“
Zvažte následující zcela vytvořený jazyk, který je v podstatě jen kalkulačka s středníky:
// source.ect 3 + 3.2; 5.0 / 1.9; 6 * 2;
počítač nepotřebuje všechny. Prostory jsou jen pro naši malichernost. A nové linky? Nikdo je nepotřebuje. Počítač změní tento kód, který vidíte, do proudu tokenů, které může použít místo zdrojového souboru., V podstatě, to se ví, že 3
je celé číslo, 3.2
je float a +
je něco, co působí na ty dvě hodnoty. To je vše, co počítač opravdu potřebuje. Úkolem lexikálního analyzátoru je poskytnout tyto tokeny místo zdrojového programu.
Jak se to dělá, to je opravdu docela jednoduché: dát lexer (méně okázalé znějící způsob, jak říct, lexikální analyzátor) některé věci očekávat, pak řekněte to, co dělat, když to vidí, že věci. To se nazývají pravidla., Zde je příklad:
int cout << "I see an integer!" << endl;
Když int přichází skrze lexer a toto pravidlo je popraven, budete uvítáni s docela zřejmé, „vidím, že číslo!“ vykřičník. To není, jak budeme používat lexer, ale to je užitečné vidět, že kód provedení je libovolná: nejsou pravidla, že budete muset udělat nějaký objekt a vrátit se to, je to jen obyčejný starý kód. Může dokonce použít více než jeden řádek tím, že jej obklopuje rovnátky.,
mimochodem, budeme používat něco, co se nazývá FLEX dělat naše lexing. Je to docela snadné, ale nic vám brání jen program, který dělá to sami.
získat pochopení toho, jak budeme používat flex, podívejte se na tento příklad:
zavádí několik nových pojmů, takže pojďme na ně:
%%
se používá k samostatné oddíly .lex soubor. První část je deklarace-v podstatě proměnné, aby byl lexer čitelnější., Je to také místo, kde importujete, obklopené %{
a %}
.
druhá část je pravidla, která jsme viděli dříve. Jedná se v podstatě o velkýif
else if
blok. Provede řádek s nejdelším zápasem. Proto, i když změníte pořadí float a int, plováky bude stále odpovídat, protože odpovídající 3 znaky 3.2
je větší než 1 znak 3
., Všimněte si, že pokud není uzavřeno žádné z těchto pravidel, přejde na výchozí pravidlo, jednoduše vytiskne znak na standard out. Poté můžete použít yytext
odkazovat na to, co viděl, že odpovídá tomuto pravidlu.
třetí část je kód, který je jednoduše C nebo C++ zdrojový kód, který je spuštěn při spuštění. yylex();
je volání funkce, která spouští lexer. Můžete také provést čtení vstupu ze souboru, ale ve výchozím nastavení čte ze standardního vstupu.
Řekněme, že jste vytvořili tyto dva soubory jako source.ect
a scanner.lex
., Můžeme vytvořit C++ programu pomocí flex
příkaz (vzhledem k tomu, že máte flex
instalované), kompilovat, že dolů a vstupní náš zdrojový kód k dosažení naší úžasné tiskové prohlášení. Dáme to do akce!
Hej, super! Právě píšete kód C++, který odpovídá vstupům do pravidel, abyste něco udělali.
jak to kompilátory používají? Obecně platí, že místo tisku něco, každé pravidlo vrátí něco-token! Tyto tokeny lze definovat v další části kompilátoru…,
analyzátor syntaxe
AKA „dělat docela zdrojový kód použitelný“
je čas se bavit! Jakmile se sem dostaneme, začneme definovat strukturu programu. Analyzátor je právě dána proud tokenů, a to musí odpovídat prvky v tomto proudu, aby se zdrojový kód má strukturu, která je použitelná. K tomu, používá gramatiky,ta věc, kterou jste pravděpodobně viděli ve třídě teorie nebo slyšeli svého podivného přítele geekinga. Jsou neuvěřitelně silní, a je toho tolik, do čeho jít, ale dám jen to, co potřebujete vědět pro náš tak trochu hloupý analyzátor.,
gramatiky v podstatě odpovídají neterminálním symbolům s nějakou kombinací terminálových a neterminálových symbolů. Terminály jsou listy stromu, non-terminály mají děti. Nebojte se o tom, pokud to nedává smysl, kód bude pravděpodobně srozumitelnější.
budeme používat generátor analyzátorů zvaný Bison. Tentokrát, rozdělím soubor do sekcí pro účely vysvětlení. Za prvé, prohlášení:
první část by měla vypadat povědomě: dovážíme věci, které chceme použít. Poté to bude trochu složitější.,
unie je mapování „skutečného“ typu C++ na to, čemu budeme říkat v tomto programu. Takže, když vidíme intVal
, můžete nahradit, že ve vaší hlavě int
, a když vidíme, floatVal
, můžete nahradit, že ve vaší hlavě float
. Uvidíš, proč později.
dále se dostaneme k symbolům. Můžete je rozdělit do hlavy jako terminály a terminály, jako u gramatik, o kterých jsme mluvili dříve. Velká písmena znamenají terminály, takže se nadále nerozšiřují., Malá písmena znamenají non-terminály, takže se i nadále rozšiřují. To je jen konvence.
každé prohlášení (počínaje %
) deklaruje nějaký symbol. Nejprve vidíme, že začínáme s non-terminal program
. Pak definujeme některé žetony. <>
držáky definovat návratový typ: INTEGER_LITERAL
terminál vrátí intVal
. SEMI
terminál nic nevrátí., Podobná věc může být provedeno s non-terminály pomocí type
, jak lze vidět při definování exp
jako non-terminál, který vrací floatVal
.
nakonec se dostaneme do precedence. Známe PEMDAS, nebo jakoukoli jinou zkratku, kterou jste se naučili,která vám řekne několik jednoduchých pravidel precedence: násobení přichází před přidáním atd. Nyní to prohlašujeme divným způsobem. Za prvé, nižší v seznamu znamená vyšší prioritu. Za druhé, možná se divíte, co znamená left
., To je asociativita: docela hodně, pokud budeme mít a op b op c
a
b
jít spolu, nebo třeba b
c
? Většina našich operátorů to první, kde a
b
jít spolu první: to je tzv. levou asociativitu. Někteří operátoři, jako umocňování, udělat pravý opak: a^b^c
očekává, že zvýšíte b^c
a^(b^c)
. S tím se však nebudeme vyrovnávat., Podívejte se na stránku Bison, pokud chcete více podrobností.
dobře, pravděpodobně jsem vás dostatečně nudil prohlášeními, zde jsou gramatická pravidla:
toto je gramatika, o které jsme mluvili dříve. Pokud nejste obeznámeni s gramatiky, je to docela jednoduché: na levé straně může proměnit v jakéhokoli z věcí, na pravé straně, oddělena pomocí |
(logický or
). Pokud to může jít dolů více cest, to je ne-ne, říkáme tomu nejednoznačná gramatika., Tohle není dvojznačný, protože naše přednost prohlášení – pokud změníme to tak, že plus je již vlevo asociativní, ale místo toho je deklarován jako token
SEMI
, vidíme, že dostaneme shift/reduce konflikt. Chcete vědět víc? Podívejte se, jak Bison funguje, náznak, používá algoritmus analýzy LR.
Dobře, takže exp
se může stát jeden z těchto případů: INTEGER_LITERAL
FLOAT_LITERAL
, atd. Všimněte si, že je také rekurzivní, takže exp
se může změnit na dvě exp
., To nám umožňuje používat složité výrazy, jako je 1 + 2 / 3 * 5
. Každý exp
, nezapomeňte, vrátí Typ plováku.
to, co je uvnitř závorek, je stejné jako u lexeru: libovolný kód C++, ale s podivnějším syntaktickým cukrem. V tomto případě máme připravené speciální proměnné s $
. Proměnná $$
je v podstatě to, co se vrací. $1
je to, co je vráceno prvním argumentem, $2
co je vráceno druhým atd., „Argument“ myslím částí gramatiky pravidlo: takže pravidlo exp PLUS exp
má argument 1 exp
, argument 2 PLUS
a argument 3 exp
. Takže v našem provedení kódu přidáme výsledek prvního výrazu ke třetímu.
nakonec, jakmile se vrátí zpět naprogram
non-terminal, vytiskne výsledek příkazu. Program je v tomto případě spousta příkazů, kde příkazy jsou výrazem následovaným středníkem.
nyní napíšeme kódovou část., To je to, co bude skutečně spuštěno, když projdeme analyzátorem:
dobře, začíná to být zajímavé. Naše hlavní funkce nyní čte ze souboru poskytnutého prvním argumentem namísto ze standardu v, a přidali jsme nějaký chybový kód. Je to docela samozřejmé a komentáře dělají dobrou práci při vysvětlování toho, co se děje, takže to nechám jako cvičení čtenáři, aby na to přišel. Vše, co potřebujete vědět, je, že jsme se vrátili k lexeru, abychom poskytli žetony analyzátoru! Zde je náš nový lexer:
Hej, to je vlastně menší teď!, Vidíme, že místo tisku vracíme terminálové symboly. Některé z nich, jako ints a plave, jsme první nastavení hodnoty před přechodem na (yylval
je návratová hodnota terminálu symbol). Kromě toho jen dává analyzátoru proud terminálových tokenů, které lze použít podle svého uvážení.
Cool, umožňuje spustit pak!
jdeme na to-náš analyzátor vytiskne správné hodnoty! Ale to není opravdu kompilátor, to prostě běží C++ kód, který provádí to, co chceme. Chcete-li vytvořit kompilátor, chceme to změnit na strojový kód., K tomu musíme přidat trochu víc…
do příště…
nyní si uvědomuji, že tento příspěvek bude mnohem delší, než jsem si představoval, takže jsem si myslel, že to tady ukončím. V podstatě máme funkční lexer a analyzátor, takže je to dobrý bod zastavení.
vložil jsem zdrojový kód na můj Github, pokud jste zvědaví na zobrazení konečného produktu. Jak více příspěvků jsou uvolněny, že repo uvidí více aktivity.,
Vzhledem k naší lexer a parser, můžeme nyní vytvářet meziprodukt zastoupení našeho kódu, který lze nakonec převést do skutečného strojového kódu, a já vám ukážu, jak přesně to udělat.
další zdroje
pokud náhodou chcete více informací o čemkoli, co je zde uvedeno, propojil jsem některé věci, abych mohl začít. Šel jsem hodně, takže je to moje šance ukázat vám, jak se ponořit do těchto témat.
Oh, mimochodem, pokud se vám nelíbily mé fáze kompilátoru, zde je skutečný diagram. Stále jsem vynechal tabulku symbolů a popisovač chyb., Všimněte si také, že mnoho diagramů se od toho liší, ale to nejlépe ukazuje, o co se zajímáme.
Napsat komentář