Dienstag, 23. Oktober 2007

Weitere Sprachfeatures

Nach einer etwas längeren Auszeit aufgrund von Serverproblemen ist der Blog nun wieder da. In diesem Zuge wurde der Blog auch gleich auf schönere Füße gestellt.
So ist er nicht mehr unter http://coltishware.com/~chest/ erreichbar, sondern nur noch ausschließlich unter der Subdomain http://chest.coltishware.com .
Aus diesem Grund müssen auch diejenigen, die den RSS-Feed abonniert haben, dies noch einmal tun, damit die Adressen übereinstimmen.
Sollte es noch einmal vorkommen, dass der Server ausfällt, so wird dieser Blog, wie dieses Mal, wahrscheinlich für die diese Zeit unter http://coltishware.blogspot.com zu finden sein.


Aber nun zum eigentlichen Thema...

Ich wurde letztens darum gebeten, eine Übersicht über die zeitliche Planung des Projektes abzugeben. Da solche zeitlichen Planungen sich nur selten an die Wirklichkeit halten, habe ich stattdessen einen groben Ablaufplan skizziert, der die einzelnen Phasen der Entwicklung zeigt.
  1. Sprachfeatures sammeln und in einen einheitlichen Sprach(-syntax-)kontext einfügen

  2. anhand der sprachfeatures den benötigten bytecode ausarbeiten (was muss die VM können, was kann durch Programmierung in ByteCode emuliert werden)

    1. die VM schreiben

    2. einen Source-zu-ByteCode-Compiler schreiben

  3. evtl. fehler an der Sprache ausmerzen

Zur Zeit befinden wir uns beim ersten Hauptpunkt - ohne Übersicht der Sprachfeatures kann man keine optimierte ByteCode-Sprache entwickeln.

Und da es bezüglich der Features noch etwas mager aussieht, gibt es noch ein paar neue, die ich an dieser Stelle vorstellen möchte.
Zum einen wurde noch nicht erwähnt, dass Chest case-insensitive sein wird, sprich: in der Sprache wird nicht zwischen Groß- und Kleinschreibung von Bezeichnern und Befehlen unterschieden werden.
Alles andere führt unweigerlich zu Fehlern, die auch problemlos umgangen werden können.
Desweiteren wurde ich darauf hingewiesen, dass ich mich noch nicht zu dem Thema Generics geäußert habe. Natürlich werden diese in der Sprache integriert sein.

In diesem Zuge wird ein weiteres Sprachfeature Einzug in Chest halten. An dieser Stelle möchte ich mich herzlich bei Christian Rehn (alias R2C2) bedanken, durch den ich die Idee für dieses wirklich wunderbare Sprachfeature überhaupt erst bekommen habe.
Zur Zeit hat es noch den Namen Code Injection, jedoch hoffe ich, dass mir früher oder später noch ein besserer Name dafür einfallen wird.
In bisherigen Szenarien ist es vor allem in Verbindung mit Generics aufgetreten und wird dort sicherlich gute Dienste tun.

Erst einmal ein Quelltextbeispiel, um daran die Funktion zu erklären:
//angenommen, MyArray.find ist so definiert:
find set (Result as LongInt)
provide (Item as )
retrieve (Return as Boolean) is providing;

{...}

find set (Result as LongInt)
provide (Item as )
retrieve (Return as Boolean)
variables
Index as LongInt;
variables;
begin
Result = -1;

for Index = Low(Self) to High(Self) do
begin
// if retrieve(provide(Self[Index])).Return do
if provide(Self[Index]).Return do
begin
Result = Index;

break;
end;
end;
end;

{...}

//Aufruf von MyArray.find() mit
//zusätzlich injiziertem Code
//und direktem Auslesen des
//Rückgabewertes "Result"
myResult = MyArray.find()
begin
Return = (Item.Name = 'SomeText');
end.Result;

Nun zur Erklärung des Codes:
Die Methode find() (hier von einer imaginären Klasse MyArray) ist hier als providing deklariert. Dieser Punkt ist wichtig, um dem Interpreter zu sagen, dass die Methode es erlaubt, eigenen Code in ihr auszuführen.
Zudem sind neben dem set-Feld zusätzlich noch die Felder provide und retrieve definiert. Diese sind für die Ausführung des injizierten Codes sehr wichtig: sie stellen den Kopf des injizierten Codes dar. provide steht dabei für die Werte die dem injizierten Code übergeben werden und retrieve steht für die Werte, die vom injizierten Code zurückgegeben werden.
Mit provide(Self[Index]) wird der injizierte Code aufgerufen. Da es sich intern um eine normale Methode handelt, kann demzufolge mit provide(Self[Index]).Return auch der Rückgabewert der Methode angesprochen werden. Der eigentlich ausgeführte Text ist dabei Return = (Item.Name = 'SomeText');.
Diese Variante erpart viel Tipparbeit im Vergleich zur Übergabe von Methodenreferenzen und hat zudem die Möglichkeit, dass für verschiedene Implementierungen von Generics verschieder Code eingefügt und ausgeführt werden kann.
So ist es erstmalig möglich, eine Suchfunktion in einem Generic zu implementieren und lediglich die Überprüfung der genauen Felder auszulagern.

Ich hoffe, diese Erklärung war zumindest annähernd verständlich. Das Feature selbst ist einfach zu nutzen, jedoch schwer zu erklären.
Sollte es also Fragen geben: einfach Fragen... ;-)


Bis demnächst...
Wirsing

Labels: ,

Donnerstag, 11. Oktober 2007

Erste Sprachbeispiele

updated

Ich will nur erst einmal die Informationen loswerden.
Erklären werde ich alles über ein späteres Update.

Ich dachte mir, es wäre einmal interessant, einen kleinen Einblick in den bisherhigen Sprachaufbau zu geben.

Fangen wir am besten mit dem Klassen-Aufbau an:
Wie zu erkennen ist, ist zur Zeit echte Mehrfachvererbung vorgesehen.
Wie in an C++ angelehnten Sprachen üblich, ist der eigentliche Quelltext in der Klassendefinition mit eingeschlossen.
Ein Problem, das mir bei Java aufgefallen ist, ist, dass Methoden und Variablen im ganzen Klassen-Block verstreut sein können. Da versteckt sich mitunter eine private Methode unter einem Haufen public Methoden oder vice versa.
Um den ein bisschen entgegenzuwirken und den Quelltext aufzuräumen, gibt es Sektionen - für jede Sichtbarkeitsebene (private, protected und public) jeweils eine. Jedoch mit einem Unterschied: den Bezeichnungen.
Was ist denn der Unterschied zwischen private und protected?
Warum sollte ich das eine hierfür und das andere dafür benutzen?
Um ein bisschen Licht ins Dunkel zu bringen, werden neue Bezeichner für diese Sichtbarkeits-Ebenen benutzt, die dem ganzen (hoffentlich) mehr Klarheit verschaffen. In Chest wird es daher folgende Ebenen geben:
  • implementation (= private)
    hier definierte Variablen und Methoden sind nur der Klassen-Implementation zugänglich

  • inheritor (= protected)
    hier definierte Variablen und Methoden sind nur der Klassen-Implementation und deren Erben (engl. inheritors) zugänglich

  • public
    hier definierte Variablen und Methoden sind jedem zugänglich

Desweiteren ist jede Sektion noch einmal in 2 Unter-Sektionen aufgeteilt: variables und methods. Es dürfte klar sein, dass Variablen in dem einen Block und Methoden (inkl. deren Quelltext) in dem anderen Block definiert werden.


Class-Definition:
<ClassName> inherits <ClassName> [, <ClassName>]

//... contains variables and methods that
//... are available to this class
implementation
variables
//... some variables
variables;

methods
//... some methods
methods;
implementation;

//... contains variables and methods that
//... are available to this class and
//... to inheriting classes
inheritor
variables
//... some variables
variables;

methods
//... some methods
methods;
inheritor;

//... contains variables and methods that
//... are available to all classes
public
variables
//... some variables
variables;

methods
//... some methods
methods;
public;

<ClassName>;


Nun kommen wir einmal zur Variablen-Deklaration:
Dieser ist eigentlich relativ einfach. Da Chest eine "sprechende" Programmiersprache sein soll, wird eine Variable auch einfach als ein "<Name> als <Typ>" definiert.


Variable-Definition:
<Name> as <Type>;


Auch bei der Definition von Methoden gibt es keine Zauberei:
Jedoch gibt es im Gegensatz zu anderen Sprachen keine "einfachen" Rückgabewerte mehr. Stattdessen definiert man für jede Methode eine List von Rückgabewerten. Dadurch spart man sich Parameter-Strukturen, die in anderen Sprachen Gang und Gebe sind (z.B. var-Parameter in Pascal, Reference-Übergaben wie in C, C++, etc.). Stattdessen definiert man einfach weitere Rückgabewerte.
Nun mag man sich fragen: "Wie soll man mehrere Rückgabewerte auslesen können?"
Schließlich will man eine Methode ja nicht mehrfach aufrufen, nur um dann jeweils einen weiteren Rückgabewert auszulesen, wie z.b. in:
begin
println(Calculate(5, 5).Add);
println(Calculate(5, 5).Divide);
println(Calculate(5, 5).Multiply);
end;

Um solche Probleme zu umgehen, wird ein besonderer Trick angewendet, den ich schon länger mit mir umhertrage: "Warum betrachtet man Methoden nicht wie Klassen?"
Diese Idee ist ja teilweise schon im Umlauf - z.B. ist in Java der Konstruktur einer Klasse einfach deren Name (der Rückgabe der Methode ist eine Klasse mit dem Namen der Methode). So etwas kann man doch auch für normale Methoden einführen - eben um die Rückgabewerte darin zu speichern. Um dies zu erreichen, muss also für jede Methode ein eigener (dynamischer) Typ im Hintergrund generiert werden. Dadurch sind dann soetwas möglich:
variables
MyVariable as Calculate;
variables;
begin
MyVariable = Calculate(5, 5);

println(MyVariable.Add);
println(MyVariable.Divide);
println(MyVariable.Multiply);
end;


Nun aber noch zu ein Paar weiteren Dingen:
Neben normalen Methoden wird es noch abstrakte Methoden (is abstract) geben. Dabei handelt es sich lediglich um die Definition einer Methode ohne deren Implementierung. Dadurch ist es möglich, eine Form von Interfaces zu schreiben.
Des weiteren wurde ja bereits angesprochen, dass Chest die Möglichkeit der Mehrfachvererbung erhalten soll. Nun ist jedoch die Frage, was sein soll, wenn zwei Superklassen einer Klasse Methoden besitzen, die genau gleich definiert sind (Name und Parameterliste stimmen überein), sodass die Methoden nicht als überladen definiert sein können.
Für diesen Fall gibt es das Renaming (is <ClassName>.<MethodName>). Durch dieses wird es möglich, Methoden einer Superklasse in einem Nachkommen unter einem neuen Namen anzubieten. Diese Möglichkeit ist natürlich genau dann nützlich, wenn man Namenskonflikte bei verschiedenen Elternklassen auflösen muss.


Method-Definition:
<MethodName> get ([<Name> as <Type>;])
set ([<Name> as <Type>;])
is abstract;
<MethodName> get ([<Name> as <Type>;])
set ([<Name> as <Type>;])
is <ClassName>.<MethodName>;

<MethodName> get ([<Name> as <Type>;])
set ([<Name> as <Type>;])
variables
//... some variables
variables;
begin
//... some sourcecode
end;


Zum Schluss gibt es noch einmal ein kleines Quelltext-Beispiel.

Calling a Method
  //...

returnLongWord get (Value as LongInt;)
set (Result as LongWord;)
begin
Result = LongWord:Value;
// cast Value to a LongWord (not yet sure)
end;

//...

variables
myValueA as retunLongWord;
myValueB as LongWord;
variables;
begin
myValueA = returnLongWord(5);
println(myValueA.Result);

myValueB = returnLongWord(5).Result;
println(myValueB);

println(returnLongWord(5).Result);
end;

//...


So, damit wäre dieses größere Update auch schon zu Ende.
Ich hoffe, der Einblick in den derzeitigen Sprachaufbau war für euch interessant.

Man beachte aber bitte, dass Dinge wie "println()" und das Type-Casting nur als Platzhalter zu verstehen sind. Hier ging es erst einmal um die im Text näher beschriebenen Sprachfeatures.
Über Änderungen halte ich euch natürlich auf dem Laufenden!



Bis demnächst...
Wirsing

Labels: ,

Freitag, 5. Oktober 2007

Ideen zur Sprache

Wir haben wieder einmal Freitag: sprich, es wird Zeit für die neusten Informationen.
Heute möchte ich einmal über die Entwicklung der Sprach-Syntax schreiben.

Wie man sich vorstellen kann, ist es garnicht so einfach, in diesem Bereich Entscheidungen zu fällen: wenn man sich zu sehr an einer existierenden Sprache orientiert, kann es passieren, dass man diese einfach nur re-implementiert; jedoch muss man auch darauf achten, dass die Sprache gut zu erlernen ist - und da kann man sich bei bestehenden Sprachen ruhig eine Scheibe abschneiden.
Generell ist zudem zu beachten, dass die Sprache die gebotenen Sprachfeatures unterstützen und diese möglichst nahtlos in den Programmierfluss mit einbringen sollte.
Sprich: die Sprache ist abhängig vom Funktionsumfang der Sprache.
Daher sollte auch von Anfang an grob umrissen sein, welche Features die entsprechende Sprache unterstützen soll.
Auf ebendiese Features bin ich im letzten Posting schon einmal kurz eingestiegen.

Ein Beispiel: die Sprache soll den Speicherzugriff ermöglichen und diesen soweit möglich managen - die Speicherzugriffe selber sollten jedoch nicht zur Standard-Arbeit des Programmierers gehören. Daher wäre es hier z.B. ratsam, Bezeichner für Kern-Methoden von normalen Methoden abzuheben.
Dadurch erkennt man sofort, wo im Quelltext System-Funktionen genutzt werden - durch dieses Vorgehen kann ein Entwickler auch viel schneller mögliche Problembereiche im Quelltext ausfindig machen (moderne Programmiersprachen versuchen schließlich nicht umsonst, den Entwickler von heute von der Arbeit am Speicher abzukoppeln).

Nun aber zu einigen der im Titel versprochenen Ideen der Sprache: und da fangen wir auch direkt mit einem der wichtigsten Punkte an - das Einschließen von Code-Blöcken.
Es ist erschreckend, aber fast alle Sprachen benutzen dafür Klammern - am liebsten die geschweiften "{" und "}".
Nur einige Sprachen (z.B. Pascal) benutzen hierfür echte Codewörter.
Da diese Klammern überall im Quelltext auftauchen, prägen sie entscheidend das Erscheinungsbild einer Sprache und lassen sie dementsprechend (un-) strukturiert aussehen. Leider musste ich in meiner Laufbahn schon mehrfach feststellen, dass Programmierer Klammern zwar toll finden, da sie schnell einzutippen sind und da sie gut dafür geeignet sind, Blöcke zu kennzeichen (man schließt den Quelltext ja in einer Klammer zusammen), die meisten jedoch schnell in einen Trott bei der Klammer-Einrückung verfallen.
Warum für die eine kleine schließende Klammer nochmal eine ganze Zeile vergeuden - die sieht doch dann so leer aus. Und schon verschwindet die schließende Klammer hinter einem Befehl und fällt dort nicht weiter auf - zumindest bis diese eine Zeile bei Aufräumarbeiten gelöscht wird und der Compiler auf einmal Fehler über Fehler ausspuckt, weil irgendwo eine Klammer fehlt. Nur welche? Das ist die Frage.
Aus diesem Grund wird es in Chest Codewörter für das Bilden von Code-Blöcken geben - sie helfen dem Programmierer, eine gewisse Struktur zu wahren.

Insgesamt ist Chest als "sprechende Sprache" geplant - soweit es den Programmierfluss nicht stört, ist es angedacht, sprechende Codewörter statt kryptischer (leicht parsebarer) Sonderzeichen einzuführen und dem Programmierer auf diese Art und Weise eine Programmiersprache und keinen Programmierzeichensatz anzubieten.

So, das soll es für heute erst einmal gewesen sein...


Bis demnächst...
Wirsing

Labels: ,