next up previous contents
Next: 4 Implementacja programu Up: Język do tworzenia aplikacji Previous: 2 Cel pracy i   Spis rzeczy

Subsections

3 Opis języka TLCC

1 Wprowadzenie

W tym rozdziale omówione są konstrukcje dostępne w języku TLCC, ze szczególnym zwróceniem uwagi na podobieństwa i różnice w stosunku do C++ i Javy. Dla każdej omawianej konstrukcji podany jest przykład.

Ponieważ wynikowy kod tworzony jest w C++, więc składnia języka TLCC również jest zbliżona do C++. Jednak nie wszystkie elementy składniowe z C++ są dostępne. Przykładowo, nie ma możliwości używania wskaźników, ponieważ umożliwiają one bezpośrednie operacje w pamięci na obiektach. Tymczasem konkretny obiekt może się znajdować w innym procesie (np. na innej maszynie), więc dostęp do niego powinien się odbywać wyłącznie przy pomocy referencji systemu CORBA.

2 Definicje klas i funkcji

Każdy plik zawierający kod w TLCC jest zasadniczo ciągiem definicji klas. W pliku może również się znajdować definicja funkcji main(), która jest wykonywana w momencie tworzenia nowej sesji dla użytkownika. W definicji klasy bezpośrednio wstawia się kod metod. Oto prosty przykład definicji klasy.

class[*] A
{
private:
   int value;
public:
   A(int value)
   {
      this.value = value;
   }
   int getValue()
   {
      return value;
   }
} 

Dostępne jest, tak jak w C++, wielokrotne dziedziczenie. Składnia przekazywania argumentów do konstruktorów z nadklas jest również taka sama.

class B : A
{
private:
   string caption;
public:
   B(int value, string caption) : A(value)
   {
      this.caption = caption;
   }
}

3 Rodzaje atrybutów klas

Dostępne są następujące rodzaje pól i metod:

Oprócz tego deklaracja atrybutu może być poprzedzona słowem const. Natomiast dostępne typy danych to:

Przykład pokazuje sposób wykorzystania atrybutów statycznych i dzielonych. Jego działanie polega na zliczaniu wystąpień obiektów klasy C w poszczególnych sesjach oraz globalnie w całej aplikacji.

class C
{
private:
   static int objectsInSession = 0;
   shared int objectsInAllSessions = 0;
public:
   C()
   {
      objectsInSession++;
      objectsInAllSessions++;
   }
   static int getObjectsInSession()
   {
      return objectsInSession();
   }
   shared int getObjectsInAllSessions()
   {
      return objectsInAllSessions();
   }
} 

... 

print("In session: " + C::getObjectsInSession() +
      "In all sessions: " + C::getObjectsInAllSessions()); 

Dzięki atrybutom dzielonym możliwa jest łatwa wymiana danych między sesjami. Można tworzyć specjalne obiekty dzielone, z dzielonymi wszystkimi atrybutami, pełniące rolę bazy danych dla aplikacji.

4 Wizualizatory

Aby uprościć generowanie kodu w HTML-u można wykorzystać specjalne funkcje - wizualizatory. Umożliwiają one bezpośrednie wstawianie dowolnego napisu (w tym przypadku HTML-a) w wielu wierszach, bez konieczności stosowania cudzysłowów. Każda klasa może mieć jeden domyślny wizualizator (default visualizer) oraz wiele wizualizatorów zwykłych. Podczas działania aplikacji można przełączać wizualizatory przy pomocy wbudowanej funkcji set_visualizer(). Kolejny przykład demonstruje klasę z wizualizatorami.

class Node
{
private:
   int value;
public:
   Node()
   {
      value = 0;
   }
   default visualizer showPlus()
   [
      <p>Value is: #value#.</p>
      <p>It is greater or equal 0.</p>
   ]
   visualizer showMinus()
   [
      <p>Value is: <font color="red">#value#</font>.</p>
      <p>It is less than 0.</p>
   ]
   void setValue(int value)
   {
      this.value = value;
      if (value >= 0)
         set_visualizer(showPlus);
      else
         set_visualizer(showMinus);
   }
   int getValue()
   {
      return value;
   }
} 
 
W przedstawionym kodzie znaki # pełnią specjalną funkcję, ponieważ pozwalają umieszczać wyrażenia lub instrukcje wewnątrz kodu HTML. Jeśli będzie to wyrażenie, to jego wartość dopisze się w odpowiednim miejscu do generowanego przez wizualizator napisu. Natomiast gdy są to instrukcje, to zostaną wykonane. Bloki '#' oraz bloki wizualizacji '[]' można wielokrotnie zagnieżdżać. Oto przykład takiej konstrukcji:

visualizer showMultiplicationTable()
[
   <table align="center">
   #{
      for(int y = 1; y <= 10; y++)
      [
         <tr>
         #{
            for(int x = 1; x <= 10; x++)
               [<td align="center">#x*y#</td>]
         }#
         </tr>
      ]
   }#
   </table>
]

Przykład ten jest wizualizatorem, który wyświetla tabliczkę mnożenia. Przejście z trybu wstawiania kodu w HTML-u do trybu instrukcji odbywa się jedynie przy pomocy znacznika '#'. W szczególności wstawioną instrukcją może być blok '{}', tak jak w podanym przykładzie.

Przy pomocy funkcji set_root() można ustawić korzeń wizualizacji, czyli obiekt, od którego rozpoczyna się wyświetlanie strony WWW. Aby wewnątrz wizualizatora pewnego obiektu wyświetlić inny obiekt należy użyć wbudowanej funkcji visualize(). W ten sposób wszystkie obiekty, które reprezentują poszczególne elementy strony mogą tworzyć drzewo wyświetlania. Można również wprowadzić takie zależności między obiektami, aby graf wyświetlania nie był drzewem (ponieważ będzie zawierał cykle). Oto przykładowy kod, który korzysta z klasy Node.

class Service
{
private:
   Node node;
public:
   Service()
   {
      node = new Node();
   }
   default visualizer show()
   [
      <html>
      <head></head>
      <body>
         #visualize(node)#
      </body>
      </html>
   ]
} 

void main()
{
   set_root(new Service());
} 
 
Wraz z definicją klasy Node przedstawiony kod stanowi kompletny program w tym języku. Na razie jego działanie jest bardzo proste - sprowadza się do wypisania informacji, że bieżąca wartość jest równa 0. W kolejnych punktach tego rozdziału klasa Service zostanie zmodyfikowana w taki sposób, aby można było zmieniać tą wartość.

5 Obsługa akcji

Aby obiekty wchodzące w skład strony WWW były interaktywne, trzeba skojarzyć z poszczególnymi odnośnikami ciągi instrukcji (akcje). Gdy użytkownik klika na odnośnik, to aplikacja wykonuje związany z nim kod. Łatwo można zmodyfikować wizualizator w klasie Service tak, aby były dostępne odnośniki pozwalające zwiększać i zmniejszać bieżącą wartość.

class Service
{
   ... 

   default visualizer show()
   [
      <html>
      <head></head>
      <body>
         #visualize(node)#
         <p><a href=#action{node.setValue(node.getValue()+1)}#>
            increment value</a></p>
         <p><a href=#action{node.setValue(node.getValue()-1)}#>
            decrement value</a></p>
      </body>
      </html>
   ]
} 
 
W bloku, który występuje po słowie action można umieszczać dowolne ciągi instrukcji. Mogą one korzystać ze zmiennych lokalnych zadeklarowanych w wizualizatorze, przy czym istotna jest wartość takiej zmiennej podczas tworzenia dowiązania (wartość ta zostaje przekazana w tym odnośniku). Gdy mamy bardzo dużo różnych akcji, to takie podejście pozwala obsługiwać je lokalnie w obiektach, a nie ręcznie kodować w URL i rozpoznawać podczas przyjmowania żądania HTTP przez aplikację (opis protokołu HTTP znajduje się w pozycji [13]). Łatwo można także wykorzystać w akcjach zmienne lokalne, których wartości są automatycznie kodowane w URL.

6 Obsługa formularzy

Tworzenie formularzy i odczytywanie z nich danych również jest ułatwione. Z poszczególnymi elementami formularza przy pomocy funkcji field możemy wiązać zmienne, na które będą automatycznie przypisane odpowiednie wartości, gdy użytkownik wyśle ten formularz. Istnieje także możliwość umieszczania w formularzu akcji przy pomocy bloku form_action. Zatem klasę Service można rozszerzyć w taki sposób, aby użytkownik mógł podać wyświetlaną wartość.

class Service
{
   ... 

   default visualizer show()
   [
      <html>
      <head></head>
      <body>
         #visualize(node)#
         <p><a href=#action{node.setValue(node.getValue()+1)}#>
            increment value</a></p>
         <p><a href=#action{node.setValue(node.getValue()-1)}#>
            decrement value</a></p> 

         #int value#
         <p>
         <form action=#form_action()# method="get">
            New value:
            <input type="text" name=#field(value)#
               value=#node.getValue()#>
            <input type="submit" value="Set value">
            #form_action{node.setValue(value)}#
         </form>
         </p>
      </body>
      </html>
   ]
} 
 
W podanym przykładzie wykorzystywana jest zmienna lokalna value, na którą pobierana jest wartość podana przez użytkownika. Pierwsze wywołanie funkcji form_action() w nagłówku formularza powoduje wygenerowanie odpowiedniego URL dla atrybutu action. Wewnątrz formularza można używać funkcji field(), żeby utworzyć nazwę pola formularza (atrybut name). Na końcu w bloku form_action umieszcza się akcję wykonywaną po wysłaniu formularza (zmienna value ma już odpowiednią wartość, więc można z niej skorzystać).

7 Tworzenie większych aplikacji

1 Dzielenie programu na moduły

W celu tworzenia w przedstawionym przeze mnie języku większych aplikacji, niezbędna jest możliwość dzielenia kodu źródłowego na moduły. Zatem potrzebna jest instrukcja, która pozwoli dołączyć jakieś inne pliki, zawierające dodatkowe klasy. W TLCC dostępna jest specjalna instrukcja uses. Oto przykład jej użycia.

uses utils in "modules/utils.tlcc"; 

... 

... new utils::Dictionary(); 

...

Na początku pliku deklarujemy moduły, z których korzystamy. Podajemy przy tym nazwę modułu (przy pomocy której będziemy się do niego odwoływać w programie) oraz (opcjonalnie) ścieżkę do pliku. Podczas translacji na C++ nastąpi sprawdzenie, czy moduł ten nie wymaga rekompilacji.

Natomiast w dalszej części tego przykładu pokazana jest składnia używana przy odwoływaniu się do klasy z modułu. Jeśli w innych używanych modułach nie ma klasy o tej samej nazwie, to można używać bezpośrednio nazwy klasy, bez konieczności poprzedzania ją nazwą modułu.

2 Wstawianie kodu w C/C++

Użyteczną rzeczą jest możliwość wykorzystania istniejących bibliotek dla C/C++. Dlatego w TLCC powinna być możliwość umieszczania kodu napisanego bezpośrednio w C/C++. Kod taki nie jest tłumaczony przez translator, tylko przepisywany do pliku wynikowego. Aby wstawić kod w C/C++ należy umieścić go między znacznikami '@'. Oto przykład:

@#include <stdio.h>@ 

... 

void main()
{
   ... 

   @printf("Hello world");@
} 

Aby zbyt często nie łączyć kodu napisanego w TLCC z kodem w C/C++ najlepiej napisać pomocniczy moduł, który pośredniczy w wywołaniach funkcji z zewnętrznych bibliotek. Kolejny przykład zawiera fragment takiego modułu, który obsługuje funkcję printf().

@#include <stdio.h>@ 

class System
{
   static void echo(string str)
   {
      @printf((char *) str);@
   }
} 
 
W ten sposób można prosto zaimplementować obsługę np. bazy danych, korzystając z bibliotek dla C/C++. Istnieje również możliwość napisania narzędzia, które na podstawie plików nagłówkowych będzie generowało odpowiednie moduły.

Oto przykład pomocniczej klasy pośredniczącej między bazą danych MySQL [10] a programem napisanym w języku TLCC.

@
#include<iostream>
#include<iomainp>
#include<sqlplus.hh>
@ 

class Database
{
private:
   @
   Connection       connection;
   Query            query;
   Result           result;
   Result::iterator iter;
   Row              row;
   @ 

public:
   Database(string db, string host, string user, string passwd)
   {@
      connection.connect((char *) db, (char *) host, (char *) user,
         (char *) passwd);
   @}
   void executeQuery(string content)
   {@
      query = connection.query();
      query << (char *) content;
      result = query.store();
      iter = result.begin();
   @}
   void resetIterator()
   {@
      iter = result.begin();
   @}
   void hasMoreRows()
   {@
      return iter != result.end();
   @}
   void loadNextRow()
   {@
      row = *iter;
      iter++;
   @}
   string getField(string name)
   {@
      return CORBA::string_dup(row[(char *) name]);
   @}
   string getField(int index)
   {@
      return CORBA::string_dup(row[index]);
   @}
}

3 Zewnętrzne wizualizatory

Często zdarza się, że do tworzenia stron WWW wchodzących w skład serwisu używane są specjalne edytory generujące kod HTML. Zatem wygodnie by było wykorzystywać pliki utworzone przez edytory jako wizualizatory w aplikacji. Jest to bardzo proste, oto przykład:

visualizer showPage() in "page.html";

Zamiast pełnej definicji wizualizatora podajemy po prostu nazwę pliku, w którym się znajduje. W takim pliku-wizualizatorze można wstawiać wszystkie konstrukcje dostępne w zwykłych wizualizatorach.


8 Złożone typy danych

Dzięki złożonym typom danych oraz specjalnym operacjom na nich, zarządzanie informacjami zgromadzonymi w obiektach jest łatwiejsze. W tym punkcie zostaną omówione napisy, obiekty, struktury i tablice.

W TLCC używanie napisów jest wygodniejsze niż w C++, dzięki specjalnemu typowi string. Oto przykłady operacji na łańcuchach:

string str = "abc"; // ustawienie wartości zmiennej
str += "def"; // dołączenie napisu
int length = str; // pobranie długości napisu
char c = str[2]; // pobranie litery 'c'


Natomiast tworzenie i usuwanie obiektów jest tak łatwe jak w C++, mimo że w TLCC są to obiekty systemu CORBA. Wystarczy użyć odpowiednio operatorów new i delete. Istnieją jednak specjalne odmiany operatora new. Jedna z nich pozwala utworzyć dany obiekt nie w bieżącym procesie, ale w pewnym (najmniej obciążonym) serwerze obiektów dzielonych. Dzięki temu można uzyskać równomierne rozmieszczenie obiektów dzielonych na serwerach, przez co obsługa danych jest rozproszona. Operator ten nazywa się new_shared. Jeszcze inną odmianą tego operatora jest new_separated, dzięki której utworzony obiekt będzie odseparowany od bieżącej sesji użytkownika, w której domyślnie tworzone są nowe obiekty. W takim przypadku obiekt ten nie będzie odczytywany z dysku wraz z sesją, lecz dopiero w momencie, gdy wystąpi odwołanie do niego.

W TLCC można korzystać ze struktur, które odpowiadają strukturom udostępnianym przez środowisko CORBA. Ich zadaniem jest grupowanie danych, dzięki czemu można np. przekazać w wyniku funkcji więcej informacji. Struktury są zawsze przekazywane przez wartość, a więc odwołania do nich są zawsze lokalne. Oto przykład definicji struktury.

struct Example
{
   string str;
   int value;
} 

Ostatnim typem danych, który pozostał do omówienia w tym punkcie są tablice. W TLCC są dostępne dodatkowe operacje na tablicach, dzięki którym można dynamicznie zmieniać ich wielkość. Dlatego tablica może służyć do grupowania danych w obiekcie, podobnie jak tabela w bazie danych. Oto składnia tych operacji:

string[] strings; // utworzenie pustej tablicy napisów
strings <- "abc"; // dodanie napisu do tablicy
int length = strings; // pobranie wielkości tablicy (length=1)
strings-; // zmniejszenie wielkości tablicy o 1

Przykłady zastosowań złożonych typów danych w konkretnej aplikacji znajdują się w rozdziale 5.


9 Zarządzanie sesjami

W TLCC dostępne są wbudowane funkcje, które pozwalają określić parametry sesji. Funkcja transient_session() powoduje, że bieżąca sesja będzie usunięta po dłuższym czasie nieaktywności. Funkcję tą można wywołać z parametrem, który określa liczbę sekund, jaką sesja musi być nieaktywna, aby została usunięta. Istnieje także funkcja persistent_session(), która sprawia, że bieżąca sesja nie będzie skasowana.

10 Trwałość obiektów

Wszystkie obiekty tworzone przez użytkownika są obiektami systemu CORBA. Oznacza to, że można się do nich swobodnie odwoływać (mając referencję do takiego obiektu) z dowolnego procesu (nawet na innej maszynie). Ponadto dzięki zastosowaniu mechanizmu POA, który jest częścią standardu CORBA, wszystkie obiekty są trwałe - będą automatycznie zapisane na dysku przed usunięciem z pamięci. Potrzeba taka zachodzi wtedy, gdy dany obiekt przez pewien czas nie jest używany lub w przypadku restartu serwera.

Dzięki temu, że obiekty są trwałe, wszystkie dane użytkownika w serwisie WWW mogą być przechowywane w jego obiektach. Zamiast używać bazy danych można więc wykorzystać obiekty do składowania różnych informacji. Jest to prostsze rozwiązanie niż odwzorowywanie obiektów na tabele oraz każdorazowy odczyt/zapis tych danych z/do bazy, gdy nadchodzi żądanie od użytkownika. Ponadto jeśli chcemy zaprogramować serwis WWW w sposób obiektowy (np. przy pomocy J2EE), to będziemy musieli na podstawie bazy danych odtwarzać obiekty użytkownika, które realizują funkcjonalność aplikacji i następnie po wykonaniu określonej akcji, zapisywać je do bazy. Takie podejście jest bardzo niewygodne w implementacji i wymaga napisania sporej ilości pomocniczego kodu (do komunikacji z bazą).

Automatyczny zapis obiektów na dysk w TLCC działała w taki sposób, że dla każdego serwera obiektów oraz serwera obiektów dzielonych wykorzystywany jest specjalny plik, który pełni rolę składnicy obiektów. Wszystkie czynności związane ze składowaniem obiektów są wykonywane automatycznie.

Ponadto dzięki zastosowaniu technologii CORBA, widoczność wszystkich tworzonych obiektów nie ogranicza się tylko do napisanej aplikacji. Obiekty te mogą być współdzielone przez wiele aplikacji napisanych w tym języku, a nawet przez dowolne inne programy. Dzięki temu, że CORBA obsługuje wiele języków programowania (w przeciwieństwie do RMI w Javie) jest możliwość napisania programu, np. w C++ lub w Javie, przetwarzającego dane, które są zawarte w obiektach dzielonych. Aby otrzymać referencję do obiektu dzielonego wystarczy wysłać odpowiednie żądanie do serwera. Analogicznie, gdy chcemy otrzymać jakieś informacje o obiektach wchodzących w skład sesji użytkowników, to wywołujemy metody z serwera obiektów (np. pobranie sesji o określonym identyfikatorze lub korzenia wizualizacji dla pewnej sesji).

11 Uzasadnienie wyboru składni języka

Przy wyborze języka docelowego dla translatora brałem pod uwagę C++ i Javę. Zdecydowałem się na C++, ponieważ jest on szybszy od Javy. Wstępne testy pokazały, że implementacja MICO CORBA [9] (wykorzystująca C++) jest english razy szybsza niż CORBA używana przez IBM J2RE 1.3.0. Ponadto zwykły kod pisany w C++ jest również znacząco szybszy.

Język TLCC jest podobny do Javy, jeśli pominiemy dodatkowe konstrukcje związane z wizualizacją i składowaniem danych. Łatwo można by go zmodyfikować w taki sposób, aby miał on składnię taką samą jak Java. Jednak miałoby to sens tylko wtedy, gdyby można było wykorzystywać klasy biblioteczne Javy (np. klasę Object zamiast wbudowanego typu object). Niestety nie jest to możliwe, ponieważ zakładam, że do wszystkich używanych klas można się odwoływać zdalnie. Natomiast obiekty biblioteczne w Javie nie mogą być obsługiwane w ten sposób - wymaga to napisania odpowiednich interfejsów i dodatkowego kodu związanego z systemem CORBA wewnątrz klas, których używamy (czyli zmiany implementacji tych klas). W przeciwnym razie nie będziemy mogli takiego zewnętrznego obiektu swobodnie używać, ze względu na to, że odwołania do niego mogą pochodzić z innego procesu (być może na innej maszynie).

Zatem lepiej tłumaczyć TLCC na C++ i zapewnić, że jego składnia będzie zbliżona do C++. Jednak pewne ograniczenia są niezbędne, ponieważ kod, który piszemy zostanie przetłumaczony zgodnie z wymaganiami technologii CORBA. Nie jest możliwe używanie wskaźników, ponieważ dają one możliwość bezpośredniego dostępu do pamięci obiektów. Natomiast w wynikowym programie użytkownik nie korzysta z obiektu, który stworzył przy pomocy operatora new, lecz z pewnego obiektu pomocniczego, który jest jedynie pośrednikiem między oryginalnym obiektem a kodem użytkownika.

To samo dotyczy wzorców klas w C++ (ang. templates) i przeciążania operatorów. Jednak te wszystkie wymienione cechy nie są konieczne nawet przy pisaniu dużych aplikacji. Dowodem jest Java, w której żadnej z tych cech nie ma. Jedyne większe różnice (pomijając konstrukcje związane z wizualizacją i składowaniem danych) między TLCC a Javą są takie, że Java posiada interfejsy i nie umożliwia wielodziedziczenia. Natomiast w TLCC istnieje wielodziedziczenie, a nie ma interfejsów - czyli tak jak w C++.

Dostępne są natomiast pozostałe konstrukcje z C++, w szczególności wszystkie rodzaje instrukcji, operatory, typy itp. Usuwanie obiektów jest jawne (operator delete), ponieważ mamy do czynienia z rozproszonymi obiektami. Zatem reasumując:

  1. Cechy, które posiada C++, a nie ma TLCC (i Java):
  2. Cechy, które odróżniają język TLCC od Javy:


next up previous contents
Next: 4 Implementacja programu Up: Język do tworzenia aplikacji Previous: 2 Cel pracy i   Spis rzeczy
Paweł Lenk 2002-12-10