Project SyntaxPainter

I. Introduction

Oczywiste jest, że gdy opracowujemy stronę internetową, na której poruszamy tematykę programowania, nierzadko zachodzi potrzeba prezentacji naszych kodów źródłowych wprost w przeglądarce. Prosta aplikacja, którą napiszemy będzie doskonałym narzędziem do tego typu zadań – wystarczy wskazać wcześniej przygotowany dokument XML, w którym zawrzemy opis składni konkretnego języka programowania (czyli listę słów kluczowych i operatorów, czy dopuszczalne nazwy identyfikatorów) oraz tekst programu, by po jednym kliknięciu otrzymać czytelny i kolorowy kod HTML gotowy do umieszczenia na naszej witrynie.

W niniejszym artykule krok po kroku przedstawię proces powstawania tego programu. Część zagadnień pominę, gdy uznam, iż są one na tyle oczywiste, by Czytelnik mógł się z nimi zapoznać jedynie poprzez analizę dołączonego kodu źródłowego (który dodatkowo wzbogaciłem o szczegółowe komentarze).

Uważam, że najlepiej od razu przejść do praktyki zwłaszcza, że temat, który chcę omówić w pierwszej kolejności jest łatwy i nie wymaga zbyt obszernego komentowania (przynajmniej w tym konkretnym przypadku). Mam mianowicie na myśli

II. User interface

Introduction

Jak już wcześniej wspomniałem nie będziemy korzystać z narzędzia Designer dostępnego w Visual Studio. Dlaczego? Z dwóch powodów – po pierwsze uważam, że w celach edukacyjnych każdy powinien napisać przynajmniej kilka prostych programów tworząc interfejs zupełnie od podstaw, a po drugie daje nam to niezależność od samego środowiska, za które musimy zapłacić (do użytku komercyjnego) w przeciwieństwie do samego kompilatora języka C#, który dostarczany jest wraz ze środowiskiem uruchomieniowym Microsoft .NET Framework za darmo. Tak więc nasz program możemy napisać nawet w notatniku ;) Jeśli jednak ktoś jest niecierpliwy, to poniżej zamieszczam zrzut ekranu, na którym umieściłem nazwy wszystkich kontrolek, więc nic nie stoi na przeszkodzie, by skorzystać z Designera i przejść do zasadniczej części artykułu.

Screen 1
Rys. 1. Interfejs użytkownika

Let's start coding

Zabierzmy się zatem do pracy – w Visual Studio tworzymy nowy, pusty projekt Visual C#. Dodajmy wpierw referencje do bibliotek, z których poźniej będziemy korzystać (w oknie Solution Explorer klikamy prawym przyciskiem myszy na napis References i wybieramy Add reference. Z zakładki .NET wybieramy następujące biblioteki – System, System.Drawing, System.Windows.Forms oraz System.Xml. Następnym krokiem jest dodanie do projektu pliku kodu źródłowego (menu Project -> Add New Item -> Code file). Utworzenie okna jest bardzo proste – wystarczy napisać klasę, która będzie dziedziczyć z System.Windows.Forms.Form i w konstruktorze ustawić te właściwości, które są nam potrzebne oraz dodać kontrolki potomne - będą one prywatnymi (w większości) składowymi tejże klasy. Spójrzmy zatem na szkielet naszej aplikacji, w którym dodatkowo umieściłem już punkt wejścia do naszego programu, czyli statyczną metodę o prototypie public static void Main() (możemy ją umieścić w dowolnej klasie o dostępie publicznym).

Uwaga dla osób, które używają Designera – będzie konieczna nieznaczna modyfikacja wygenerowanego automatycznie kodu. Zwróćmy bowiem uwagę na to, że niektóre pola składowe klasy MainForm są publiczne (w dalszej części artykułu napiszemy klasę Painter, w której znajdą się odwołania do tych pól – z tego samego powodu nadaliśmy nazwę obiektowi form i uczyniliśmy go publiczną, statyczną składową klasy MainClass).

Initialization of the main form

W tej chwili zajmiemy się prywatną metodą InitalizeUI(), w której tworzymy kontrolki potomne. Generalnie, aby utworzyć kontrolkę należy zrobić zazwyczaj cztery rzeczy: dynamicznie utworzyć obiekt danej klasy, ustawić pożądane właściwości (takie jak rozmiar, pozycja czy tekst), napisać procedury obsługi interesujących nas zdarzeń oraz referencję nowoutworzonego obiektu dodać do kolekcji kontrolek potomnych okna macierzystego (w naszym przypadku będzie to główne okno aplikacji). Ostatnia czynność jest konieczna z tego względu, że dopiero wtedy system operacyjny weźmie na siebie odpowiedzialność za podstawową funkcjonalność kontrolek (czyli np. wyświetlanie na ekranie, czy obsługa podstawowych zdarzeń, takich jak kliknięcie myszą). Nie będę tutaj przytaczał całej implementacji tej metody, gdyż jest ona oczywiście dostępna w załączonym kodzie źródłowym. Pokażę jedynie trzy przykłady – ustawimy właściwości naszego okna oraz utworzymy przycisk zamykający aplikację i pole tekstowe, w którym znajdować się będzie ścieżka dostępu do pliku wejściowego. Resztę kontrolek tworzymy analogicznie, w razie potrzeby sięgając do dokumentacji .NET Framework Class Library.

Porada
Po wpisaniu operatora += przy dodawaniu procedury obsługi zdarzeń naciśnij dwukrotnie TAB, aby od razu przejść do implementacji tejże procedury.

// Ustawiamy pożądane właściwości głównego okna.
// Zauważmy, że implicite odwołujemy się tutaj do
// instancji klasy MainForm - np. napisy Text, czy Size
// są tłumaczone na this.Text i this.Size odpowiednio.
Text = "Syntax Painter v1.0 written by Immortal";
StartPosition = FormStartPosition.CenterScreen;
Size = new Size(310, 190);
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;

// przycisk wyświetlający okno dialogowe
inputBrowseButton = new Button();
inputBrowseButton.Text = "...";
inputBrowseButton.Location = new Point(inputTextBox.Right + 5,
                                       inputTextBox.Top);
inputBrowseButton.Size = new Size(25, 20);
inputBrowseButton.Click += new EventHandler(inputBrowseButton_Click);

// pole edycyjne, w którym będzie ścieżka dostępu do pliku wejściowego
inputTextBox = new TextBox();
inputTextBox.Location = new Point(schemesLabel.Right + 20,
                                  inputLabel.Top - 3);
inputTextBox.Width = 165;
inputTextBox.ReadOnly = true;
inputTextBox.BackColor = Color.White;

// dodajemy kontrolki do okna macierzystego
Controls.Add(inputBrowseButton);
Controls.Add(inputTextBox);

Note
Kontrolki będą otrzymywały Focus w takiej samej kolejności w jakiej dodawaliśmy je w tym miejscu (można to zmienć ustawiając właściwość TabOrder).

Dotychczas nie wspominałem o oknach dialogowych. Tworzy się je jednak prawie tak samo jak zwykłe kontrolki, z tą różnicą, że nie musimy dodawać ich do kolekcji kontrolek okna macierzystego. Do ich wyświetlania służy metoda System.Windows.Forms.CommonDialog.ShowDialog().

Obtaining list of available schemas

Kilku słów komentarza wymagać może funkcja LoadSchemes(), która wyszukuje w folderze Schemes wszystkie dokumenty XML i ich nazwy dodaje do listy rozwijanej schemesComboBox. Statyczna metoda Directory.GetFiles() zwraca tablicę nazw plików znajdujących się w podanym folderze (opcjonalnie podajemy wzorzec, według którego odbywać się będzie wyszukiwanie – w tym wypadku „*.xml”), natomiast nie trzeba chyba wyjaśniać przeznaczenia funkcji Path.GetFileNameWithoutExtension().

III. Lexical structure of programming languages and XML.

Introduction

Pierwszą czynnością jaką wykonuje niemalże każdy kompilator jest analiza leksykalna tekstu źródłowego, czyli jego podział na tzw. tokeny (pojedynczym tokenem jest na przykład identyfikator, słowo kluczowe, znak + lub literał całkowitoliczbowy). W tej i kolejnej cześci artykułu postaramy się zrobić dokładnie to samo. Wyodrębnimy pewne, stałe elementy dowolnego języka programowania i określimy dla nich cechy takie jak kolor czy krój czcionki. Z dwóch powodów umieścimy wszystkie potrzebne nam informacje w dokumencie XML – po pierwsze jest to uniwersalny format przechowywania danych, z którym warto jest się zapoznać, a po drugie manipulacja danymi przechowywanymi w takiej postaci jest wyjątkowo prosta i przyjemna przy użyciu bibliotek dostępnych na platformie .NET.

Ze względu na ograniczenie objętości artykułu nie będę tutaj przytaczał zawartości omawianych dokumentów XML – jeśli jeszcze tego nie zrobiłeś(-aś), to zachęcam do pobrania kodów źródłowych i innych plików, by móc do nich zaglądać w trakcie dalszej lektury.

Schema file format

Jako przykład posłuży nam język C#. Przyjrzyjmy się zatem zawartości pliku C#.xml znajdującego się w folderze \Schemes. Cechą charakterystyczną każdego dokumentu w formacie XML jest jego drzewiasta struktura (w przeciwieństwie do języka HTML tutaj każdy znacznik musi zostać zamknięty, konstrukcja <foo /> jest tylko skrótem i oznacza <foo></foo>), co obrazuje poniższy schemat.

Screen 2
Rys. 2. Schemat pliku c#.xml

Poniżej krótkie opisy tego, co znajduje się w poszczególnych znacznikach:

  • Extensions – rozszerzenia używane przez dany język programowania (w tym wypadku jest to cs),
  • BuiltIn – informacje ogólne na temat końcowego wyglądu dokumentu HTML (kolor tła, krój i rozmiar czcionki itd.) oraz rodzaje tokenów wbudowanych. Nie możemy zmieniać nazw znaczników, ani dodawać własnych, możemy jedynie modyfikować wartości atrybutów (nie dotyczy to oczywiście znaczników Keyword i Operator),
  • UserDefined – rodzaje tokenów zdefiniowane przez użytkownika (tutaj możemy zmieniać nazwy znaczników, a także dodawać nowe, ale musimy pamiętać o dostarczeniu wszystkich atrybutów – najlepiej skopiować którykolwiek wiersz oraz wedle uznania zmienić nazwę znacznika i wartości atrybutów.

Jeśli chodzi o atrybuty, to myślę, że większość z nich powinna być zrozumiała. Nadmienię tylko, że jako wartości atrybutów specyfikujących wygląd tokenów możemy przyjmować dowolne wartości dopuszczalne w formacie CSS (kaskadowe arkusze stylów), czyli np. wartością atrybutu BgColor może być nie tylko predefiniowana nazwa (np. Black), ale także wartość szesnastkowa (np. #FFFFFF) oraz kolor w formacie RGB (np. rgb(255, 255, 255)). Znaczenie znaczników RegExpr i Priority stanie się jasne, gdy przejdziemy do czwartej części artykułu o wyrażeniach regularnych.

XML in practice

Zobaczmy teraz jakich mechanizmów dostępnych na platformie .NET możemy użyć, aby wydobyć z dokumentu XML interesujące nas informacje. Proponuję w tej chwili zajrzeć do kodu źródłowego i zapoznać się z polami i metodami składowymi klasy Painter (w niej znajdują się statyczne metody wykonujące czarną robotę, czyli wczytywanie danych, analizę leksykalną i generowanie kodu HTML) oraz klasy Token (która posłuży nam do przechowywania informacji na temat wszystkich rodzajów tokenów występujących w danym języku programowania).

To co nas interesuje znajdziemy w przestrzeni nazw System.Xml, a klasy, którymi się teraz zajmiemy to System.Xml.XmlDocument, System.Xml.XmlNode oraz System.Xml.NodeList. Wszystko co musimy zrobić to utworzyć obiekt klasy XmlDocument, załadować dokument i swobodnie nawigować po jego drzewie rozbioru. Nasze skromne potrzeby nie wymagają jednak rekurencyjnej wędrówki po drzewie – całą pracę zrzucimy na bibliotekę .NET – potrzebna nam funkcja to GetElementsByTagName(string) klasy XmlDocument, która zwraca listę wszystkich węzłów w drzewie o zadanej nazwie (jeśli wiemy, że w dokumencie na pewno znajduje się dokładnie jeden znacznik o podanej nazwie, to od razu możemy skorzystać z metody Item klasy XmlNodeList, aby odwołać się do pierwszego (zerowego) elementu na liście). W wyżej opisany sposób odczytujemy z dokumentu informacje globalne:

    // otwieramy dokument XML
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.Load(schemeFileName);
    XmlNode node = null;

    // wczytujemy z dokumentu XML ogólne informacje (kolor tła,
    // rozmiar tabulatora oraz krój i rozmiar czcionki)
    bgColor = xmlDoc.GetElementsByTagName("BgColor").Item(0).InnerText;
    tabSize =
        Int32.Parse(xmlDoc.GetElementsByTagName("TabSize").Item(0).InnerText);
    fontFamily =
        xmlDoc.GetElementsByTagName("FontFamily").Item(0).InnerText;
    fontSize =
        Int32.Parse(xmlDoc.GetElementsByTagName("FontSize").Item(0).InnerText);

Dostęp do atrybutów węzła również nie jest skomplikowany. Służy do tego właściwość Attributes klasy XmlNode. Aby wczytać słowa kluczowe i operatory skorzystamy z właściwości ChildNodes klasy XmlNode, która zwraca listę wszystkich dzieci danego węzła (znacznika), którą przeglądamy przy pomocy pętli foreach (po uzyskaniu wszystkich informacji tworzymy nowy obiekt klasy Token i wstawiamy go na listę tokenTypes:

    // wczytujemy z dokumentu XML słowa kluczowe danego języka programowania
    node = xmlDoc.GetElementsByTagName("Keywords").Item(0);
    tokenTypes.Add(new Token(node.LocalName,
    node.Attributes["RegExpr"].Value,
    node.Attributes["BgColor"].Value,
    node.Attributes["Color"].Value,
    node.Attributes["Bold"].Value == "true" ? "Bold" : "None",
    node.Attributes["Italic"].Value == "true" ? "Italic" : "None",
    node.Attributes["Underline"].Value == "true" ? "Underline" : "None",
    Int32.Parse(node.Attributes["Priority"].Value)));
    foreach (XmlNode childNode in node.ChildNodes)
        keywords.Add(childNode.InnerText);

IV. Regular expressions and lexical analysis

Theory

Myślę, że zanim przejdziemy do omawiania praktycznych zagadnień warto wpierw zaznajomić się z odrobiną teorii języków formalnych :) Formalnie, gdy mówimy o językach, mamy na myśli pewien podzbiór zbioru wszystkich słów nad jakimś alfabetem (słowo oznacza tutaj dowolny ciąg symboli danego alfabetu). W latach 1956-1958 Noam Chomsky i John Backus opracowali teorię gramatyk formalnych – jest to pewien, matematyczny sposób na skończony opis wszystkich słów należących do języka. Wprowadzona została pewna hierarchia służąca do klasyfikacji języków. Chomsky wyróżnił 4 typy, które przytaczam poniżej (nie będę zagłębiał się w szczegóły – jeśli ktoś jest zainteresowany, to na końcu artykułu znajdzie odnośnik do materiałów na ten temat):

  • typ 0 – gramatyki struktur frazowych,
  • typ 1 – gramatyki kontekstowe,
  • typ 2 – gramatyki bezkontekstowe (najczęściej stosowane w praktyce – np. do opisu składni języków programowania mimo, że języki programowania są w większości tak na prawdę kontekstowe; taki stan rzeczy spowodowany jest trudnością, jaką sprawia komputerom analiza języków kontekstowych nie wspominając już o strukturach frazowych),
  • typ 3 – gramatyki regularne.

Okazuje się, że algorytmiczna analiza języków opisywanych przez gramatyki regularne jest stosunkowo bardzo prosta – w dużym uproszczeniu można powiedzieć, że aby rozstrzygnąć, czy dane słowo należy do języka, wystarczy jeden przebieg „maszyny” (słowa tego używam nie bez przyczyny – najczęsćiej algorytmy te implementuje się przy użyciu tzw. automatów skończonych), która „zjada” znak po znaku. Nietrudno jest się domyślić skąd wzięła się nazwa wyrażeń regularnych – jedno z podstawowych twierdzeń teorii brzmi następująco: język daje się opisać za pomocą gramatyki regularnej wtedy i tylko wtedy, gdy istnieje wyrażenie regularne opisujące ten język.

Trzeba jednak pamiętać, iż twierdzenie to jest prawdziwe tylko wtedy, gdy rozważamy wyrażenia regularne w ich pierwotnej postaci wprowadzonej przez matematyków, ponieważ wyrażenia regularne, których używa się współcześnie (np. na platformie .NET, czy w systemach UNIX) są o wiele bogatsze i występują w nich konstrukcje, za pomocą których da się opisać języki, które nawet nie są bezkontekstowe).

Basic regular expressions

Same wyrażenia buduje się za pomocą pewnych symboli (w naszym przypadku za pomocą znaków ASCII), więc mówi się także o języku wyrażeń regularnych – jest to tzw. metajęzyk, czyli język, którego używamy do opisu innego języka. Zacznijmy może od podstawowych konstrukcji, o których wspomniałem w powyższym komentarzu (przyjmijmy, że naszym alfabetem jest alfabet złożony z małych liter alfabetu angielskiego: {a, b, c, ..., z}). Indukcyjna definicja wyrażeń regularnych jest następująca:

Wyrażeniem regularnym jest:

  1. dowolny symbol alfabetu – dopasowuje się do samego siebie,
  2. jeśli e1 i e2 są wyrażeniami regularnymi, to jest nim także:
    • alternatywa: e1 | e2 – dopasuje się do słowa opisanego przez wyrażenie e1 lub e2
    • konkatenacja: e1e2 – dopasuje się do słowa, które jest złączeniem (konkatenacją) dwóch słów dających się opisać przez wyrażenie e1 i e2 odpowiednio,
    • domknięcie Kleene’ego: e1* - dopasuje się do ciągu złożonego z dowolnej liczby słów opisywanych przez wyrażenie e1,
    • (e1) – wyrażenie ujęte w nawiasy.

Po takiej dawce teorii czas chyba przyjrzeć się jakimś przykładom (zwróćmy uwagę na priorytety poszczególnych konstrukcji - * wiąże najsilniej, potem konkatenacja, a najsłabiej alternatywa; z kolei dzięki nawiasom możemy wymusić kolejność wiązania):

  • a*b – np. aaaaaab, ab, b, ...
  • a*(b* | c) – np. aaabbbbb, abbbb, aac, ...
  • ba(ca | d)* - np. ba, baca, bad, bacacaca, badcacad, ...

Regular expressions in .NET

Na początek opiszę sposób korzystania z wyrażeń regularnych na platformie .NET, a potem przejdziemy do budowania bardziej skomplikowanych wyrażeń używając konstrukcji, które udostepnili nam programiści z Microsoftu.

Interesuje nas przestrzeń nazw System.Text.RegularExpressions, w której znajdziemy klasy Regex, Match i MatchCollection. Aby skorzystać z wyrażeń regularnych najpierw musimy takowe mieć w postaci napisu (string) i utworzyć obiekt klasy Regex. W najbardziej rozbudowanej wersji konstruktora podajemy dwa parametry – pierwszy to nasze wyrażenie regularne, a drugi to różnorakie opcje (enumeracja RegexOptions). W naszej aplikacji skorzystamy z trzech flag:

  • RegexOptions.Compiled – przyspiesza dopasowywanie wzorca, ale więcej czasu zajmuje inicjalizacja; w naszym przypadku inicjalizujemy tylko raz, a wyszukujemy wielokrotnie, więc rozsądnie jest użyć tej flagi, bowiem zysk jest zauważalny,
  • RegexOptions.IgnoreCase – ignoruje wielkość liter w przeszukiwanym tekście,
  • RegexOptions.Multiline – zmienia znaczenie konstrukcji $ i ^ - poznamy je za chwilę.

Natomiast najczęściej używane metody klasy Regex są następujące (oczywiście funkcje są przeciążone, więc po dokładny wykaz parametrów należy zajrzeć do dokumentacji):

  • bool IsMatch(...) – sprawdza, czy w tekście znajduje się przynajmniej jedno słowo, które zostanie dopasowane,
  • Match Match(...) – zwraca pierwsze (domyślnie od lewej) dopasowanie w tekście,
  • MatchCollection Matches(...) – zwraca kolekcję wszystkich dopasowań w tekście,
  • string Replace(...) – zamienia wszystkie dopasowania w tekście na inny, podany jako parametr, napis.

.NET extensions

Poznamy teraz konstrukcje, które pozwalają na budowanie bardziej skomplikowanych wyrażeń regularnych:

  • znaki różne od . $ ^ { [ ( | ) * + ? \ dopasowują się do siebie samych (aby przesłonić specjalne znaczenie tych znaków należy poprzedzić je znakiem \ ),
  • przy ustawionej fladze RegexOptions.Multilne znak . dopasowuje się do dowolnego znaku różnego od \n (nowey wiersz), w przeciwnym przypadku dopasowuje się do dowolnego znaku,
  • [aeiou] – dopasowuje się do dowolnego, wymienionego znaku,
  • [^aeiou] – dopasowuje się do dowolnego znaku różnego od wymienionych,
  • w nawiasach kwadratowych możemy dodatkowo używać znaku – oznaczającego zakres, np. [0-9], [a-f],
  • znak ^ ($) oznacza, że dopasowanie musi zaczynać się na początku (końcu) tekstu (RegexOptions.SingleLine) lub wiersza (RegexOptions.MultiLine),
  • znak * oznacza 0 lub więcej powtórzeń (np. (foo)* dopasuje się między innymi do foofoofoo),
  • znak + oznacza 1 lub więcej powtórzeń,
  • znak ? oznacza 0 lub 1 powtórzenie,
  • {n} oznacza dokładnie n powtórzeń (np. (foo){2} dopasuje się tylko do foofoo),
  • {n,} – oznacza przynajmniej n powtórzeń,
  • {n, m} – oznacza przynajmniej n, ale nie więcej niż m powtórzeń.

Wspomnę jeszcze o konstrukcjach *? i +? – oznaczają tzw. dopasowanie leniwe. Najlepiej pokazać czym różnią się one od * i + na konkretnym przykładzie. Rozważmy wyrażenia regularne ”.*” i ”.*?” oraz napis: ”foo bar” bas”. Pierwsze wyrażenie dopasuje się do całego napisu, natomiast drugie tylko do ”foo bar”.

The essence of the application

Zbierzmy razem wszystkie informacje na temat wyrażeń regularnych i zobaczmy, jak wykorzystamy je w naszym programie do analizy leksykalnej. Ponieważ powoli kończy mi się miejsce, więc w kodzie źródłowym wyodrębniłem region o nazwie Important – zachęcam więc w tej chwili do jednoczesnej analizy tego fragmentu.

Wyrażenia regularne opisujące poszczególne elementy języka programowania (atrybut RegExpr w dokumencie XML) są inicjowane w konstruktorze klasy Token. Natomiast analiza leksykalna odbywa się w sposób następujący. Próbujemy po kolei dopasować każdy rodzaj tokenów (pętla foreach) i wybieramy dopasowanie najbliższe (właściwość Index klasy Match wskazuje na pozycję pierwszego znaku, który został dopasowany). Dalsze przeszukiwanie tekstu (przechowywanego w zmiennej input) zaczyna się od miejsca, w którym skończyliśmy poprzedni przebieg (zapamiętujemy to w zmiennej currentIndex). Szczególnym przypadkiem, o którym warto wspomnieć, jest znalezienie dwóch różnych tokenów zaczynających się na tej samej pozycji – to tutaj właśnie rozstrzygnięcie daje nam atrybut Priority – wybrany zostanie ten token, który ma większy priorytet (dobrym przykładem jest komentarz // i operator dzielenia / - chcemy, aby w takiej sytuacji został dopasowany komentarz). Po wybraniu najbliższego dopasowania musimy do zmiennej output wypisać ten fragment kodu, który nie został dopasowany, a potem znaleziony token ująć w znacznik HTML (<span class=”rodzaj_tokenu”>token</span>) i dołączyć do tekstu wynikowego (aby zapewnić poprawne wyświetlanie wszystkich znaków w przeglądarce musimy jeszcze w tym miejscu dokonać zamiany znaków specjalnych HTML na ich kodowe odpowiedniki – zajmuje się tym metoda HtmlEncode). Zwróćmy także uwagę na fakt, iż używamy tutaj klasy StringBuilder, aby efektywnie wykonać wiele operacji konkatenacji napisów. W kodzie źródłowym znajdują się bardziej szczegółowe informacje na temat sposobu analizy leksykalnej, więc tam odsyłam zainteresowanych.

Screen 3
Rys. 3. Analiza leksykalna.

V. Summary

Mam nadzieję, że udało mi się w przystępny sposób opisać temat kolorowania składni. Poniżej znajdziecie odnośnik do archiwum zawierającego projekt Visual Studio. Dołączyłem także przykładowe dokumenty XML ze schematami języków programowania takich jak ANSI C, Microsoft C, C++, Microsoft C++, C#, Visual Basic .NET i kilku innych (znajdują się one w folderze SyntaxPainter\bin\Debug\Schemes). Z braku miejsca nie opiszę użytych tam przeze mnie wyrażeń regularnych – pozostawiam to Czytelnikowi jako zadanie do rozwiązania na ćwiczeniach ;)

Jeśli ktoś chce stworzyć własny dokument XML z opisem składni języka programowania, to musi pamiętać o tym, żeby zamiast znaków <, >, & używać <, > i &, a także znaki specjalne wymienione w części IV o wyrażeniach regularnych poprzedzać znakiem \.

VI. Download

Archived executable: SyntaxPainter BIN.zip
Source code: SyntaxPainter SRC.zip

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-