BlogAko som vytvoril jednoduchý kompilátor

Ako som vytvoril jednoduchý kompilátor

Ako som vytvoril jednoduchý kompilátor 2

Markdown do HTML

Inšpirácia

Môj kolega, ktorý je zároveň aj mojím  ̶s̶e̶f̶o̶m̶ mentorom naprogramoval skvelý kompilátor. Nie je to obyčajná webovka ktorá ukladá a čita z databázy.

JE TO KOMPILÁTOR. Používaný v produkčnom prostredí. A nie je to projekt typu Roslyn ( C# komplitátor) za ktorým stojí komunita, alebo veľká korporácia

Ako som vytvoril jednoduchý kompilátor 4

Osobne si myslím, že je to celkom frajerina. A aj ja by som rád z času na čas nakódil nejakú frajerinu. Tak som sa rozhodol, že aj ja si naprogramujem kompilátor!

Tak som ho teda naprogramoval…čo com z toho mal?

  • Naučil som sa o mágii zvanej kompilatory
  • Nemusel som sa učiť nový a ten istý JavaScriptový framework
  • Dosiahol som jeden z mojich cieľov
  • Naprogramoval som niečo iné
  • Viac do hĺbky som pochopil kód na ktorom pracujem

Ako som to spravil

Základom môjho kompilátora je postavený na Irony. Je to nástroj vďaka ktorému môžete implementovať ďalšie jazyky. Bohužiaľ k tomuto frameworku neexistuje na webe moc návodov. Napriek malému množstvu dokumentácie som bol schopný definovať gramatiku jazyku ktorá spracuje vstup.

Na definíciu gramatiky používam NonTerminal Terminal triedy z Irony

//Similar definition for Italics, Underscore
private readonly NonTerminal H1 = new NonTerminalMd(nameof(H1), typeof(H1Ast));
//Similar definition for H2,H3
private readonly Terminal PlainText = new StringLiteral(nameof(PlainText), "\"", StringOptions.AllowsLineBreak, typeof(PlainTextAst));

Gramatika

Nadišiel čas zadefinovať pravidlá gramatiky Markdownu. Gramatika je definovaná v tzv. Backus-Naur-Form. Irony využíva C# naplno. Vyuziva tzv. Operator overloading aby sme mohli jednoducho definovat aj taky konstrukt ako je gramatika jazyka.

Bold.Rule = "*" + Text + "*";
Italics.Rule = "/" + Text + "/";
Underscore.Rule = "_" + Text + "_";
StyledText.Rule = Bold | Italics | Underscore;
Text.Rule = PlainText | StyledText;

H1.Rule = "#" + Text;
H2.Rule = "##" + Text;
H3.Rule = "###" + Text;

ListItem.Rule = "-" + Text;

MarkdownElement.Rule = Text | H1 | H2 | H3 | ListItem;
MarkdownContent.Rule = MakeStarRule(MarkdownContent, MarkdownElement);
Root = MarkdownContent;

Naozaj sa to ľahko číta. Pozrime sa na to.

Hrubý text začína hviezdičkou * za ním nasleduje Text ktorý musí byť znova ukončený hviezdičkou *. Text môze byť StyledText alebo PlainText. StyledText môže byť Bold, Italics alebo Underscore. Vidíte tam ten maličký hack?. Zrazu môžeme mať Bold Italic text ktorý je aj Underscore. Rovnako aj Header! Chcete mat Header ktorý bude aj Bold? Žiadny problém. Všetky tieto možnosti sú v kostolom poriadku s našou gramatikou.

Ako som vytvoril jednoduchý kompilátor 6
Obrázok 1 Nie je to až TAK komplikované

Ak ste si všimli druhý parameter pri definícii NonTerminal Terminal, tak je typu AstNode. AST je skratka pre Abstract Syntax Tree Strom abstraktnej syntaxe. Potreboval som priradiť Terminal a NonTerminal uzly mojho zparsovaného textu na strom abstraktnej syntaxe.

A bola to bolesť.  Fakt to bolelo. Bol som na druhej stránke Googlu, vynadali mi na StackOverflow takmer som použil Bing. Nočná mora. Musel som sa uchýliť k riešeniu ktoré som považoval za tzv. overkill. Ako keď idete zabiť komára a namiesto ruky použijete kanón. V skratke, použil  som reflekciu.

public class NonTerminalMd : NonTerminal
{
  public NonTerminalMd(string name, Type nodeType) : base(name,  nodeType)
  {
    AstConfig.DefaultNodeCreator = () =>  Activator.CreateInstance(nodeType);
  }
}

Prečo som to robil? Pretože so stromom syntaxe sa pracuje ľahšie ako so stromom parsovania.

Ako som vytvoril jednoduchý kompilátor 8
Ako som vytvoril jednoduchý kompilátor 14

O stromu abstraktnej syntaxe do HTML

Gramatika je definovaná, AST vyzerá dobre. Teraz som musel urobiť niečo čomu som sa úspešne vyhýbal asi tak 5 rokov. Musel som sa naučiť návrhový vzor Visitor. A poviem Vám…keď som to videl prvý krát niekedy v prvom alebo druhom ročníku na Fakulte Riadenia a Informatiky  v Žiline absolútne to nedávalo zmysel. Dnes po pár rokoch fulltime programovania to dáva zmysel.

Ako som vytvoril jednoduchý kompilátor 10
Takto nejako funguju visitor pattern

Tento kód v Irony hovorí o tom ako Visitor chodí na návštevy. Pozdraví sa, potom navštívi detičky a potom povie zbohom.

public virtual void AcceptVisitor(IAstVisitor visitor) {
  visitor.BeginVisit(this);
  if (ChildNodes.Count > 0)
    foreach(AstNode node in ChildNodes)
      node.AcceptVisitor(visitor);
  visitor.EndVisit(this);
}
//from https://github.com/IronyProject/Irony/blob/06a088e0199c6e67097d72512ad69e2e86f041c2/Irony.Interpreter/Ast/Base/AstNode.cs#L141

A takto funguje môj HTML Visitor. Ktorý namiesto pozdravu zapíše HTML tag daného uzla. Keď odchádza tak zapíše ukončovací HTML tag.

public void BeginVisit(IVisitableNode node)
{
    var baseAst = (node as BaseAst);
    Write(baseAst.StartTag);
}

public void EndVisit(IVisitableNode node)
{
    var baseAst = (node as BaseAst);
    Write(baseAst.EndTag);

//from https://github.com/jozefchmelar/MarkdownCompiler/blob/806379dff263f444bb1d141304f25aee06e5f410/src/Markdown.Grammar/Visitors/HtmlConcreteVisitor.cs#L7

A ked navštívi PlainTextAst tak jednoducho zapíše text ktorý našiel

public override void AcceptVisitor(IAstVisitor visitor)
{
    if(Parent is RootAst)  // in case it's top level we want to have <p> tag, if it's inside <b> tag we dont need it.
    visitor.BeginVisit(this);
    if(visitor is IAstWriteableVisitor writeableVisitor)
        writeableVisitor.Write(Text);                    /// here you output the text.
    if (Parent is RootAst)
        visitor.EndVisit(this);
}

A už len poslať Visitora na návštevu do prvých dverí

var root = parseTree.Root.AstNode as BaseAst;
var visitor = new HtmlConcreteVisitor(new StringBuilder());
root.AcceptVisitor(visitor);

Voilà!

Z tohto

#"H1 Heading"
#_"H1 undescore heading"_
##"H2 Heading"
*"This is bold text"* "and this is regular text"
"Following three items should be in a list, this is plaintext"
- "Plaintext list item"
- *"Bold List item"*
- _*"Underscore bold list item"*_
- _"Underscore list item"_
#"This is heading again"

Mame toto

<div>
   <h1>H1 Heading</h1>
   <h1>
      <u>H1 undescore heading</u>
   </h1>
   <h2>H2 Heading</h2>
   <b>This is bold text</b>
   <p>and this is regular text</p>
   <p>Following three items should be in a list, this is plaintext</p>
   <ul>
      <li>Plaintext list item</li>
      <li>
         <b>Bold List item</b>
      </li>
      <li>
         <u>
            <b>Underscore bold list item</b>
         </u>
      </li>
      <li>
         <u>Underscore list item</u>
      </li>
   </ul>
   <h1>This is heading again</h1>
</div>

Funguje to!

Ako som vytvoril jednoduchý kompilátor 12

Záver

Chcel som napísať niečo čo zoberie Markdown a urobí z toho HTML. Vybral som vhodné nástroje, vytvoril kompilátor  a hlavne som to urobil. Nemusíte vymyšlať svoj vlastný assembler len preto aby ste napísali váš parser, kompilátor, jazyk, operačný systém…

Skús to. Urob to. Vyrieš problém.

O tom to je.

I’m just ❤️really curious

Zdrojovy kod 
Povodny clanok 
Twitter  
Medium 
Mozete mi kupit kavu
Web

Dobrý článok? Chceš dostávať ďalšie?

Už viac ako 6 200 ITečkárov dostáva správy e-mailom. Nemusíš sa báť, nie každé ráno. Len občasne.

Súhlasím so spracovaním mojich osobných údajov. ( Viac informácií. )

Tvoj email neposkytneme 3tím stranám. Posielame naňho len informácie z robime.it. Kedykoľvek sa môžeš odhlásiť.

Jozef Chmelar
Jozef Chmelarhttp://jozefchmelar.com
som programator ktory veri ze je to viac o ludoch ako o kode :) moje meno sa vyskytlo v newsletteroch ako C# Digest a dotNET Weekly. bavia ma startupy, komunikacia, marketing, programovanie :) jozefchmelar.com

Čítaj ďalej: