Änderungen von B++ 1.1 gegenüber 1.0
- Neu: Native-Klasse RegEx zur Unterstützung Regularer Ausdrücke (basiert auf T-REX)
- Neu: Zusatzbibliothek BppXML (B++-Bibliothek) - XML-DOM, Parser, Serialisierung
- Änderung: Funktion call akzeptiert als ersten Parameter neben Funktionen auch Objekte, die operator() definieren.
- Bugfix: Bug bei der Division vorzeichenloser 64Bit-Ints in der emulierten 64-Bit-Arithmetik (trat auf, wenn höchstwertiges Bit tatsächlich gesetzt war)
- Bugfix: Bug bei der Serialisierung/Deserialisierung von Bytecode-Modulen mit Klassenstrukturen, die Vorwärtsdeklarationen von Klassen enthalten.
Konsequenz ist geändertes Serialisierungsformat (Klassen schreiben die Anzahl ihrer Datenmember) -> Neuübersetzen von Modulen nötig.
- Bugfix: Bug im Interpreter bei OP_NOT auf 64-Bit-Plattformen: Hier konnte 0 nicht 0 sein, wenn für den betreffenden Typ ungenutzte Bits 63-32 gesetzt waren.
Änderungen von BOB+2 gegenüber BOB+
Allgemeines
- Speicherverwaltung mit Referenzzählung
- Stack und Länge von Symbolen sind nur durch den Speicherplatz begrenzt
- Es gibt keine explizite Begrenzung der Schachtelungstiefe von Schleifen mehr.
- BOB+2 ist eine gekapselte virtuelle Maschine, von der in einem Prozess mehrere
unabhängige Instanzen existieren können. Die Kommandozeilenversion bp2.exe ist lediglich
eine Minimalanwendung, die Startparameter von der Kommandozeile übernimmt und Ein-
und Ausgaben über die Konsole abwickelt.
- Alle Textausgaben erfolgen in BOB+2 über eine abstrakte Print-Schnittstelle, die
von jeder Applikation nach Belieben implementiert werden kann. Die BOB+2-Bibliothek
bringt vordefinierte Implementierungen für Konsolenausgaben und - in der 32-Bit-Version
- für Ausgaben über einen installierbaren Callback mit. Normale Ausgaben sind von
Debug- und Fehlerausgaben unterscheidbar, was die Einbindung in Entwicklungsumgbungen
erleichtern soll.
- zusätzlicher Zeilenende-Kommentar, mit #! eingeleitet
- Pseudo-Schlüsselwörter TRON, TROFF und TRSTEP für Debugging (eingeführt mit BOB+
1.1a),
Wartefunktion für Step ist definier- und installierbar (z.B. für Umgebungen ohne
Konsole)
- Neues Schlüsselwort var
... dient der Definition lokaler Variablen;
ersetzt (oder ergänzt) Variablen-Definitionsliste im Funktionskopf (alte Form weiter
möglich)
Syntax: <varstatement> := "var" <identifier>
[ "=" <expr> ] { "," <identifier>} [ "=" <expr> ]} ";"
.
Das var-Schlüsselwort leitet eine Definitions-Anweisung ein und kann stehen, wo
eine normale Anweisung stehen kann.
<varstatement> ist auch an Stelle des Initialisierungsausdrucks einer for-Anweisung
zulässig.
Der Gültigkeitsbereich ist immer der der definierenden Funktion (nicht der Block!!).
- var und
null und void vor Deklarationen:
Dort, wo in C++ der Rückgabetyp einer Funktion (auch Member-Funktion) steht, kann
optional var oder
null stehen. In Member-Variablen-Deklarationen ist nur
var zulässig. Die Verwendung beider Schlüsselwörter ist optional
und ohne tatsächliche Bedeutung. Sie können aber die Lesbarkeit verbessern und sind
im Zusammenhang mit Doxygen von Vorteil.
- --> Neu: Auch globale Variablen können mit var, null oder function
bzw. einem Typnamen deklariert werden. Zur Initialisierung kann ein Literalwert
verwendet werden.
Wie bei liokalen Variablen oder Variablendefinitionen in Klassen, sind auch Listen
möglich, wobei alle Elemente vom gleichen Typ sind.
- --> Neu: Es ist nicht mehr möglich, globale Variablen implizit
durch Verwendung im Quellkode anzulegen - sie müssen stets deklariert werden.
- --> Neu: Es gibt eine optionale statische Typisierung. Neben
var, null, void und
function können als Typangaben auch
die Bezeichner der vordefinierten Typen int,
bool (Synonym für int), float, double, string, vector, dictionary, buffer,
charbuffer, object und FILE
verwendet werden. Außerdem kann als Typ der Bezeichner einer zum Zeitpunkt der Übersetzung
bereits deklarierten Klasse stehen, was aber letztlich nur der besseren Lesbarkeit
dient und streng genommen einfach den Typ object
meint.
Klassen- und Referenzvariablen können nicht statisch typisiert werden, dafür gibt
es schlicht kein syntaktisches Ausdrucksmittel.
Typisierungen sind überall möglich, wo bisher 'var' verwendet werden konnte - also
bei der Deklaration von Variablen (global, lokal, in Objekten, statisch oder nicht).
'Echte' Typnamen bewirken eine Überprüfung der Gültigkeit bei der Zuweisung - der
Compiler erledigt das bei der Initialisierung mit Literalen, sonst erfolgt die Prüfung
zur Laufzeit.
- --> Neu: Nicht-statische Variablen in Objektdefinitionen können
mit Literalwerten initialisiert werden (wie die statischen Variablen).
- Für Funktionen (und Operatordefinitionen) kann ein Rückgabetyp festgelegt werden, indem man
bei der Definition statt des bisher schon möglichen (optionalen) var bzw. null einen
Typ (einschließlich void und function) angibt. Für den Rückgabetyp void beschwert
sich der Compiler, falls explizit etwas zurückgegeben wird (Argument hinter return),
bei anderen Rückgabetypen beschwert er sich bei einem return ohne Argument.
- Neues Schlüsselwort function:
... erlaubt die Definition einer Funktion oder Methode als Literal. Damit können insbesondere
anonyme Funktionen als Rechtswert eines (Zuweisungs-)Ausdrucks definiert werden.
Syntax
"function" [ "[" <classid>
"]" ] [<returntype>] "(" [<argumentlist>]
[ ";" <localvarlist>] ")" "{" { <statement>} "}" .
Achtung: Das Konstrukt wird wie ein Literal behandelt und kann überall dort
vorkommen, wo ein Literal zulässig ist. Somit kann - sofern es als Abschluss einer
Anweisung notiert wird - ein nachfolgendes Semikolon erforderlich sein (trotz der
schließenden Klammer des Anweisungsblocks).
Durch die optionale Angabe einer Klasse (Identifier oder Klassenname als String),
eingeschlossen in eckige Klammern, lässt sich eine Objektmethode als Literal definieren.
Eine solche Methode kann mit Hilfe des Operators ->(expr)() im Kontext
einer Objektinstanz aufgerufen werden und ähnelt so einem Elementfunktionszeiger
in C++.
function kann auch als Typbezeichner
zur Deklaration einer Variablen oder als Returntype einer Funktion verwendet werden.
- Reservierter Bezeichner self:
Eine Funktion (oder auch Methode eines Objekts) kann sich mit "self" "(" [<argumentlist>] ")" selbst
aufrufen. Dies ist vor allem für anonyme Funktionen gedacht, die sonst keinen Möglichkeit
zum rekursiven Aufruf hätten.
self kann auch innerhalb von Member-Funktionen verwendet werden - im Unterschied
zu "normalen" Aufruden wird hier keine Suche über den Methodennamen durchgeführt,
sondern es erfolgt eine direkte Bindung des Codes. Dadurch können bei rekursiven
Aufrufen Geschwindigkeitsvorteile erzielt werden.
- Schlüsselwort static kann auch im
Rumpf einer Funktion verwendet werden und definiert dann eine oder mehrere lokale
statische Variable(n).
Syntax:
"static" [<typedefinition>] <identifier> [ "=" <litexpr>
] { "," <identifier>}
[ "=" <litexpr> ]} ";"
- Erweiterungsschnittstelle für in C++-DLLs implementierte Funktionen und Klassen
- VM hat C-API zur Verwendung in Nicht-C++-Umgebungen
- Ausnahmebehandlung (try-catch) --> neue Schlüsselwörter try, catch, throw
- Fallunterscheidung (switch-case-default) --> neue Schlüsselwörter switch, case,
default
- Unterstützung für Quelltext-Debugging:
- VM hat Funktion setGenerateLineInfo zum Einschalten der Zeileninformationen
- Wenn eingeschaltet generiert der Compiler Zeileninformationen (eigener Opcode)
- Interpreter ruft auf VM Funktion notifySourceLineChanged zurück, die ihrerseits
einen installierbaren Callback auftuft
- Kommandozeilenversion hat Option -l zum Einschalten
Literale
- Array-Literale:
Arrays bzw. Vektoren können als Literale initialisiert werden, z.B:
myArray = [1,2,3];
otherArray=["ich","du", [1,2,3],"er",'\n',0x23]; // gemischte Typen, eingebettetes
Array
- Dictionary-Literale:
myDict = { "Name" : "Müller", "Alter" : 52, Kinder
: ["Anne","Emil"]};
allgemein: { <key> : <value> [, ... ] }; <key> ist immer ein String oder Identifier,
<value> beliebig -->
entspricht JSON-Syntax
- Funktions-Literale: siehe oben (Schlüsselwort
function)
- Escape-Codes:
Zusätzlich zu den bereits in BOB+ unterstützten Escape-Codes werden beliebige Zeichencodes
erkannt, die nach einem Backslash als normale Integer-Literale notiert werden. Hexadezimale
Zeichencodes können auch nur mit x (statt 0x) beginnen.
Beispiele: "Die Zeichenkette \7kann piepen.", 'Zeilenumbruch:\0xD\xa", '\0xFF'
- Zeichenkettenliterale können wie in C++verkettet werden:
s = "Dieser String "
"gehört zusammen."
Außerdem sind mehrzeilige Strings mit Backslash am Zeilenende zugelassen.
- Literale Ausdrücke:
Mit Literalen können Ausdrücke gebildet werden (im Prinzip wie auch sonst), wobei
die Operanden selbst Literale sein müssen.
Ausdrücke rechts von Initialisierungszuweisungen (static var, static member), #defvar und
#deflit-Instruktionen werden automatisch als Literalausdrücke erkannt. An anderen
Stellen kann ein Ausdruck mit #(<litexpr>)
explizit als Literal gekennzeichnet werden.
- Vordefinierte Literale:
true = 1, false = 0
Es gibt die vordefinierten Literale UNICODE, WIN32, WINCE und MSDOS, die jeweils
den Wert 1 haben, wenn beim Kompilieren die jeweilige Gegebenheit zutrifft und 0,
wenn nicht.
Darüber hinaus existieren in Anlehnung an C99 vordefinierte Literale __func__ (string),
__FILE__ (string) und __LINE__ (int) zur Angabe von aktuellem Methoden- oder Funktionsnamen,
Dateinamen bzw. aktueller Zeilennummer während der Übersetzung. Die werte dieser
Literale weden vom Scanner gebildet.
- Außerdem gibt es die Datentypen BVT_XXX als vordefinierte Literale
- Literale vom Typ string, vector und dictionary werden beim Zugriff darauf kopiert
(damit sie nicht zur Laufzeit geändert werden können); außerdem ist die direkte
Anwendung des []-Operators auf Literale verboten (Compilerfehler). , Funktionsliterale
werden kopiert, falls sie static-Definitionen enthalten
- Ganzzahlige Literale können auch binär, in der Form 0b1001 angegeben werden und
sind 32Bit-Integer.
Verarbeitungsanweisungen
- können auch in Klassendeklarationen und Blöcken innerhalb einer Funktion vorkommen
- sinnvoll für #include
- #include ist neu und funktioniert im Prinzip wie in C/C++
- Datei wird zur Übersetzungszeit an Stelle der #include-Anweisung eingebunden
- #include <myincfile.inc> ... sucht Datei im aktuellenVerzeichnis sowie Pfad
aus BPPINC-Umgebungsvariable
- #include "myincfile.inc" ... sucht Datei im aktuellen Verzeichnis
- In beiden Varianten sind relative Pfade zugelassen
- #use "mylib.bpm" sucht im durch BPPLIB
angegebenen Pfad, nicht mehr in PATH
- #import "mydll.dll"
sucht nach DLL mit"Native"-Modul in PATH
- #deflit <identifier> <litexpr>
definiert ein benanntes Literal (alias: #define)
- #literal <identifier> <litexpr>
definiert ein benanntes Literal, sofern es nicht bereits definiert ist
- #undeflit <identifier>
hebt die Definition eines benannten Literals auf (alias: #undef)
-
Eingebaute
Datentypen
- Fließkommazahlen können den internen Typ double (8 Bytes) oder float (4 Bytes)
haben. Standard ist double.
- Zusätzliche Referenz-Datentypen:
- Buffer : Puffer für beliebige Binärdaten
- CharBuffer : Puffer für eine Menge von Textzeichen - für schnelle Zeichenkettenmanipulationen
- Dictionary : Container für Schlüssel-Wert-Paare
- Referenz-Datentypen werden normalerweise automatisch - eben per Referenzzählung
- verwaltet. Sie können explizit davon ausgenommen werden, um sie "zu Fuß" wieder
wegzuräumen.
Vordefinierte Operatoren
- Es gibt einen speziellen Referenz-Zuweisungsoperator
=>
Er kann mit allen Referenztypen verwendet werden schließt die zugewiesene Objektreferenz
von automatischen Verwaltung über die Referenzzählung aus. Er dient in erster Linie
der Lösung des Problems von zyklischen Referenzen - dazu ein einfaches Beispiel:
Es sei eine Hierarchie von gleichartigen Objekten gegeben. Ein Objekt (Element,
Knoten im Hierarchiebaum) hat eine Menge (z.B. Liste oder Vektor) von Kindelementen.
Fügt man ein neues Element als Kind eines anderen ein, so wird der Referenzzähler
des erhöht (auf eins gesetzt, falls das Element neu erzeugt wurde). Wird nun der
Wurzelknoten gelöscht, so gibt er auch seine Kindknoten frei, d.h. vermindert deren
Referenzzähler um eins. Werden sie nirgendwo sonst verwendet, sterben auch
sie usw.
Anders sähe es aus, wenn die Kindknoten immer einen Rückwärtsverweis auf den Elternknoten
hätten. Dann würde beim Setzen dieses Verweises mittels einer normalen Zuweisung
der Referenzzähler des Elternobjekts erhöht und es könnte niemals freigegeben werden,
weil ja immer mindestens noch eine Referenz darauf (nämlich die des Kindknotens)
existiert. Mit zusätzlichen Operator wird dieses Problem umgangen, indem bei der
Zuweisung der Referenzzähler eben nicht erhöht wird.
Eine weitere - wohl wesentlich seltenere - Anwendung des Operators ist es, ein Objekt
von vornherein von der Referenzzählung auszunehmen. Das kann dann nötig sein, wenn
es knappe Systemressourcen beansprucht, die unbedingt zu einem bestimmten Zeitpunkt
freigegeben werden müssen. Ein von vornherein (uns ausschließlich) mit => zugewiesenes
Objekt kann mit dem delete-Operator
explizit gelöscht werden.
- Neu ist der .-Operator für den Zugriff auf Dictionary-Elemente sowie zur Property-Implementierung
für Objekte. Dafür kann der Operator überladen werden. In der Operatorenhierarchie
liegt er auf derselben Ebene wie [].
- Der Operator delete dient ausschließlich
der Freigabe von Objekten, die explizit von der Referenzzählung ausgeschlossen wurden.
Für alle anderen Objekte ist er zwar syntaktisch zugelassen aber wirkungslos. Anders
als in BOB+ kann er auf alle dynamischen Objekte - nicht nur auf Klasseninstanzen
angewendet werden. Er ersetzt damit die Freigabefunktion
free.
- Alle arithmetischen Operatoren können mit Operanden der Typen int, float oder double
verwendet werden. Bei unterschiedlichen Typen erfolgt eine implizite Umwandlung
in den größten Typ. Dies gilt auch für den Modulo-Operator (%), der bisher nur für
Ganze Zahlen verwendbar war.
- Hinzugekommen sind kombinierte Zuweisungsoperatoren für Bit-Operationen. also
|=, &=, ~=, ^=, <<= und
>>=. Diese Operatoren sind -
wie alle Bit-Operatoren - auf Integer-Operanden anwendbar
- Operator []: Der Operator ist auf Vektoren, Strings, Buffer, CharBuffer, Dictionarys
und Objekte anwendbar.
Bei Dictionarys ist das Argument vom Typ String, bei Objekten hängt der Argumenttyp
von der Implementierung des überladenen Operators ab, für alle anderen Typen wird
ein Index vom Typ Int verwendet.
- Operator ->: Der Operator dient wie bisher zum Methodenaufruf, statt eines Bezeichners
(Methodenname) kann jedoch auf den Operator ein geklammerter Ausdruck folgen, dessen
Ergebnis der Methodenname oder eine Funktion (genauer: Methode) sein kann., z.B.:
a->("foo")(); // Methodenaufruf über Namen
a->(b)(); // Methodenname oder Methode aus Inhalt von Variable b
Funktionen
- Funktionen können wie bisher mit mehr Parametern aufgerufen werden, als formale
Parameter definiert wurden. Anders als bisher (d.h. in BOB und BOB+) werden in BOB+2
nicht die letzten sondern die ersten übergebenen Parameter an die formalen Parameter
zugewiesen.
- Vorgabeparameter, z.B. myFunc(par1, par2 = 17);
Als Vorgabeparameter kann ein literaler Ausdruck stehen.
Vorgabeparameter können - zur Dokumentation - auch bei vorwärtsdeklarationen
verwendet werden, sind dort aber bedeutungslos.
Hat eine Funktion Vorgabeparameter, so wird sie implizit immer mit mindestans allen
definierten Argumenten aufgerufen (mehr sind möglich).
Deklaration von Klassen
- BOB+2 gestattet Vorwärtsdeklarationen von Klassen im C++-Stil, z.B.
class MyClass;
- Bei Klassendeklarationen ist hinter der schließenden Klammer (}) ein optionales
Semikolon zulässig.
Es sollte dann notiert werden, wenn eine Referenz mit Doxygen erzeugt werden soll
- sonst erkennt Doxygen das Ende der Deklaration nicht.
- Auch Methoden von Klassen können Vorgabeparameter haben
- Zur Vermeidung undefinierter Zustände ist der Compiler strenger:
- Ist eine Klasse von einer Basisklasse abgeleitet, so kann sie bei einer Neudefinition
(Redefinition, Erweiterung) nicht von einer anderen Basisklasse abgeleitet werden.
- Einer Klasse, die von einer anderen Klasse als Basisklasse verwendet wird, können keine zusätzlichen nicht-statischen Member-Variablen
hinzugefügt werden.
- Bereits definierte Member-Variablen können nicht nachträglich als static umdefiniert
werden.
Überladen von Operatoren
- OP_CALL, OP_VREF, OP_VSET wie bisher
- neu OP_PREF(key), OP_PSET(key,val) zum Lesen und Schreiben von Properties. Das Überladen
erfolgt analog zu OP_VREF/OP_VSET. key ist immer ein String (Identifier
bei der Verwendung im Quelltext)
- neu - unäres Minus: OP_NEG
- neu - unäres logisches Not: OP_NOT
- neu - Inkrement,Dekrement: OP_INC, OP_DEC (pre/post-inkrement funktioniert für
alle Datentypen)
neue Operatorfunktionen OP_PINC und OP_PDEC für explizize Definition der post-Varianten
Wird für eine Klasse OP_INC bzw. OP_DEC definiert, ohne auch OP_PINC bzw. OP_PDEC
zu definieren,
so fügt der Compiler entsprechende Definitionen ein, die den Code von OP_INC/OP_DEC
mitbenutzen.
Achtung: umgekehrt gilt das nicht! Definiert man z.B. nur OP_PINC, so führt der
Präfix-Aufruf zu einem Fehler.
Gennerell ist es ratsam, stets beide Varianten zu definieren.
Die post-Varianten Operatorfunktionen sollten immer ein neues Objekt (anders als
in C++ das modifizierte, wobei das aktuelle unverändett bleibt!), die Prefix-Varianten dagegen eine Referenz auf das eigene
Objekt zurückliefern.
class A
{
A();
OP_INC();
OP_PINC();
OP_DEC();
OP_PDEC();
setI(val);
getI();
toString();
i;
}
A::A() { i = 0; }
A::setI(val) { i=val; }
A::getI() { return i; }
A::OP_INC()
{
++i;
return this;
}
A::OP_PINC()
{
var result = new A();
result->setI(i+1);
return result;
}
A::OP_DEC()
{
--i;
return this;
}
A::OP_PDEC()
{
var result = new A();
result->setI(i-1);
return result;
}
Achtung: Das zurückgegebene Objekt ERSETZT das ursprüngliche. Dies lässt sich vermeiden,
wenn man statt eines neuen Objekts das aktuelle - also this zurückgibt. Dann arbeitet
allerdings die Postfix-Variante nicht mehr korrekt, genauer: sie verhält sich wie
die Präfix-Variante.
- binäre Operatoren (+ - * / % & | ^ << >>) können für linken und
rechten Operanden überladen werden:
OP_ADD_R OP_ADD OP_SUB_R OP_SUB OP_MUL_R OP_MUL OP_DIV_R OP_DIV OP_REM_R OP_REM
OP_BAND_R OP_BAND OP_BOR_R OP_BOR OP_XOR_R OP_XOR OP_SHL_R OP_SHL OP_SHR_R OP_SHR
- neu - unäres bitweises NOT (~): OP_BNOT
- neu - compare-Funktion zum Überladen der Vergleichsoperatoren für Objekte
- neu - equals-Funktion zum Überladen der Operatoren == und !=. Sind sowohl equals
als auch compare
implementiert gewinnt equals. equals sollte 1 für Gleichheit und 0 für Ungleichheit
zurückgeben.
- neues Schlüsselwort operator zur syntaktischen
Vereinfachung der Operatordefinition:
opdef ::= [var] "operator" opspec "("argumentlist")".
opspec ::= (["L" | "R"] binop) | indexop | propop | callop | unaryop.
binop ::= "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "<<" | ">>".
indexop ::= "[" ["var"] "]"[ "="].
propop ::= "."["="] .
callop := "(" ")" .
unaryop := "-" | "~" | "++" | "--".
Ähnlich C++ werden Pre- und Postfix-Variante der Increment/Dekrement-Operatoren
durch die Argumentliste unterschieden. Für die Postfix-Varianten enthält die Liste
das Schlüsselwort "var", für die Prefix-Varianten bleibt sie leer.
Der unäre Minus-Operator wird vom binären dadurch unterschieden, dass bei ersterem
die Argumentliste leer ist.
Alle Operatordefinitionen werden vom Compiler in die korrespondierenden reservierten
Funktionsnamen umgesetzt.
Beim Indexoperator und beim Property-Operator werden Get- und Set-Varianten dadurch unterschieden, dass bei letzteren hinter dem Operator ein Gleichheitszeichen (=)
notiert wird. Für den Indexoperator kann stattdessen auch zwischen den eckigen Klammern das Schlüsselwort "var" stehen.
Das Präfix"R" kennzeichnet bei binären Operatoren die Variante für den rechtsseitigen
Operanden, "L" oder kein Präfix die für den linksseitigen.
Ausnahmebehandlung
- Prinzip und Syntax analog C++/JavaScript
- Da es keine strenge Typisierung gibt, gibt es zu jedem try nur einen catch-Block.
- Fest implementierte ("native") Funktionen führen bei Fehlern nicht mehr zwangsweise
zum Programmabbruch sondern lösen eine Ausnahme vom String-Typ aus, die im Code
behandelt werden kann.
Beispiel:
try
{
myVar = anyFunc();
if (myVar) < 0)
throw "myVar must not be negative";
// do something else
}
catch (e)
{
print(e, "catched\n");
throw; // rethrow exception
}
Fallunterscheidung (switch-case)
- syntaktisch wie C/C++, Java(Script) u.ä.
- Anders als in C++ kann hinter case
nicht nur eine Konstante sondern ein beliebiger Ausdruck stehen. Der Ausdruck hinter
switch kann ebenfalls beliebigen Typs
sein, sofern ein Vergleich mit den Werten hinter
case definiert ist.
- Wie in C/C++ gibt es ein "fall through", d.h. wird ein
case-Zweig nicht mit break
verlassen, werden die Anweisungen des folgenden Zweiges mit ausgeführt.
Beispiel:
switch (myVariable)
{
case 1:
// do something
break;
case 2:
// do any other
// fall through
default:
// do something else
}
Neue vordefinierte globale Variablen
Die Variablen (die eigentlich als Konstanten zu verstehen sind) sollen helfen, plattformunabhängige
Programme zu schreiben. Anders als die vordefiniertten Literale bekommen sie ihren
Wert nicht zur Kompilier- sondern zur Laufzeit. Ein Programm kann so beispielsweise
prüfen, ob es in der selben Umgebung abläuft, in der es übersetzt wurde.
- __OSTYPE__ für den Typ des Betriebssystems, mögliche Werte "WIN32", "WINCE", "DOS"
oder eine leere Zeichenkette für andere Plattformen (tritt praktisch nicht auf)
- __UNICODE__ (long) mit Wert 1, wenn das Programm auf einem UNICODE-Build läuft,
sonst 0.
Vordefinierte Funktionen
- newbuffer, newcbuffer, newdict zum Erzeugen von Binär- und Zeichenpuffern
sowie Dictionarys
- newstring kennt drei Aufrufvarianten:
- newstring(string|buffer|charbuffer) ... neuer String aus einfachem Wert
- newstring(int size, fillchar=' ') ... string mit Anfangsgröße und Initialisierung
- newstring(formatStr, ...) ... String aus Formatangabe und Werteliste (analog sprintf)
- size - liefert Anzahl der Elemente eines Containers (Binär- oder Zeichenpuffer,
Vektor, Dictionary oder string)
- cb = CBuf(...) // argtypes: int, buffer, charbuffer, string; other ignored
... konstruiert einen Zeichenpuffer aus einer Argumentliste
- dictcontains, dicterase, dictclear,dictgetkeys, dictgetvalues ... Dictionary-Funktionen
- buffer(var) ... Typumwandlung in Buffer
- bool(var) ... Typumwandlung in Booleschen Wert, liefert 0 für numerische Werte ==0,
null und alle Null-Zeiger, sonst 1
- clone(var) ... erzeugt eine (tiefe) Kopie von Objekten, Strings, Vektoren, Dictionaries;
für alle anderen Typen wird einfach der Wert kopiert.
- LoadLibrary, FreeLibrary, GetProcAddress, CallLibFunc ... DLL-Funktionen ;
in Windows-Builds wird ? im Funktionsnamen bei GetProcAddress durch A (Ansi) bzw.
W (Unicode) ersetzt, unter WinCE darf die aufgerufene DLL-Funktion max. 16 Argumente
haben
- In allen Windows-Builds neue Funktionen GetUser32Func, GetKernel32Func, GetShell32Func,
GetGdi32Func und GetOle32Func. Alle diese Funktionen liefern einen Zeiger auf eine
Funktion aus der entsprechenden DLL (ähnlich GetProcAddress), wobei aber Laden und
Verwalten der DLLs intern erfolgt. Außerdem gibt es einen Alias GetCoreFunc, der
GerUser32Func entspricht. Unter WindowsCE bezoehen sich alle Aufrufe an GetUser32Func,
GetKernel32Func, GetShell32Func und GetGdi32Func auf die coredll-Bibliothek (daher
auch der Alias-Name).
- Ebenfalls in Windows-Builds: neue Funktionen GetLastError (Wrapper für die gleichnamige
Windows-Funktion) sowie GetSysErrorMessage([errcode]) zum Holen der Text-Fehlermeldung
zu einem Code. Fehlt errcode, so wird implizit GetLastError aufgerufen.
- neue Systemfunktionen getenv(string name) und setenv(string name, string value);
unter WinCE wird Environment in HKCU\Environment nachgebildet
- neue I/O-Funktionen
- long fread(buffer,[[unsigned size=1,]unsigned cnt,]FILE* fp) ... aus Datei in
Puffer lesen
- long fwrite(buffer,[[unsigned size=1,]unsigned cnt,]FILE* fp) ... aus Puffer in
Datei schreiben
- in Windows-Builds: getwc, putwc, fgetws, fputws ... Unicode-File-I/O
- neue String-Funktionen (* .. Quellstring str wird modifiziert)
- strfind(str,searchstr) ... suchen, liefert Position oder -1
- substr(str,startpos,length=-1) .. Teilstring bilden
- strreplace (str,search,replace) ... Teilstring oder Zeichen ersetzen (*)
- strerase(str,startpos,cnt=-1) ... Teilstring löschen (*)
- strinsert(str,pos,insertstr) oder strinsert(str,pos,insertchar,cnt=1) .. Einfügen
(*)
- Die Typumwandlungsfunktion string hat (wie in Version 1.0) nur ein Argument und
akzeptiert die Typen int, double, float, string ,cbuffer bzw.object. Objekte müssen
zur Umwandlung in eine Zeichenkette eine Methode toString implementieren. Für die
Formatierung von Strings ist newstring verwendbar.
- Die Typumwandlungsfunktion int versteht als Argument auch Strings mit Zahlen in
Binärdarstellung (siehe Literale).
- Die print-Funktion ruft für Objekte deren toString-Methode auf (falls es eine gibt)
Aufbau des Bytecode-Vektors einer Funktion
Index | Symbol | Beschreibung |
0 | IDX_CODE | Bytecode-Puffer (BppBuffer) |
1 |
IDX_CLASS |
Verweis auf Klasse (bei Mehoden), sonst null |
2 |
IDX_NAME |
Funktionsname
|
2 |
IDX_FIRSTLITERAL |
erstes Literal (=Funktionsname) |
3 |
IDX_DEFAULTARGS |
Vektor mit default-Argumentwerten |
4 |
IDX_SELFREFERENCE |
Zeige auf eigenen Code-Vektor (self-Referenz) |
... |
|
Werte von Literalen und lokalen statischen Variablen |
(last) |
|
Vector(Vector(paramnames),Vector(localvarnames)) - nur wenn _genLocalVarInfo in
Compiler gesetzt; wenn statische lokale Variablen existieren, enthält localvarnames
ein zusätzliches Element (letztes, Dictionary) mit Zuordnungen der Namen der lokalen
statischen Variablen zu Indizes im Vektor. |
Stack-Frame bei Funktionsaufruf
Index |
Beschreibung |
BP |
Objektverweis bei Methodenaufrufen |
BP+1 |
1. Argument |
BP+nargs |
letztes Argument) |
FP (= BP+nargs) |
vor erster lokalen Variablen (falls vorhanden) |
FP+1 |
erste lokale Variable (reserviert für throw Exception) |
... |
lokale Variablen (mit erstem opcode eingefügt) |
SP |
aktueller Stack-Pointer |
Weitere Vorhaben
- einheitliche Integer-Datentypen / uint-Typen für alle Plattformen und explizite
Zeichentypen
char, uchar, wchar, short, ushort, int, uint, long, ulong, longlong, ulonglong,
size_t, ptrdiff_t (Voraussetzung für 64Bit-Version)
int8, uint8, int16, int16, int32, uint32, int64, uint64
--> sbyte, byte, short, ushort, int, uint, long, ulong,
intptr
- Konvertierungsfunktionen und Konvertierungsoperatoren
(für Klassen) für diese Typen
- expliziter Zeigertyp void*
- statische Typisierung für Verweistypen BVT_XXXREF
- Zeiger- und Referenztypen in der Sprache (Notation wie in C++), Referenzparameter
für Funktionen
--> bisherige *REF-Typen werden Zeiger, Referenzen gibt es extra
Referenzen speichern intern einen Zeiger auf das 'richtige' Datum
--> es wird ein const& gebraucht, nicht konstante Referenzen
müssen immer auf L-Werte verweisen.
- statische Initialisierungsabschnitte für jedes Modul - werden beim Laden ausgeführt
- wie in C++
- Globaler Variablenbereich ist selbst eine Klasse (kann im Prinzip auch Member-Funktionen
und -Variablen beinhalten?)
--> Klassen müssen innerhalb anderer Klassen (hierarchisch) definierbar sein
- Namensräume - using-Anweisung
- Echte Sichtbarkeitsstufen (public, protected, private)
--> Problem: es ändert sich die Semantik der Operatoren . und ->. Beide müssen
eigentlich einen Verweis auf das angesprochene Datum liefern; -> dient also nicht
mehr zwingend dem Methodenaufruf
--> Es bleibt bei der alten Semantik. operator. sucht aber zuerst nach zugänglichen
daten-Membern (:: für statische Elemente), der benutzerdefinierte Operator wird
nuch verwendet, wenn nichts Passendes gefunden wurde.
- "Vernünftige" Implementierung von operator++(int) - wie in C++
- Inline-Definition von Methoden in Klassen
- Statt operator++/--(var) bzw. [var] soll auch
int als Argument möglich sein
- Definition von Klassen in Klassen
- echter Strukturtyp - Einzelheiten sind noch zu klären (vielleicht eine Art Klasse
mit einem buffer als Datenträger)
- Iteratoren für alle Container als Sprachmittel
- Wiederverwenden gleichartiger Literale in Codevektoren
für alle Typen (nicht nur
Strings und Variablenverweise)
--> Vektoren brauchen ein allgemeines find/rfind
- kann dann auch in der Sprache
verfügbar sein
--> besser noch: Vergleichoperatoren für BppValue, find in allen containerartigen
Typen)
- Standardklassen Vector, String, Dictionary ... als fester Sprachbestandteil
- Literale mit Endungen f/F-float, l/L-long oder long , ll/LL-longlong, u/U-unsigned,
Zifferngruppierung 24'345'123
ecplizite Unicode-Unterstützung mit L und \uxxxx bzw. \Uxxxxxxxx in Zeichen(ketten)literalen
- auto-Typ (behält nach Zuweisung statisch den Typ des zugewiesenen
Ausdrucks)
- $-Zeichen als gültiges Zeichen
in Identifiern
- NaN-Unterstützung
- Aufzählungstypen --> Realisierung noch unklar
- virtual nicht als Standard, sondern explizit zu deklarieren
--> VMTs statt Namenssuche (konkrete Realisierung noch offen)
- complex-Datentyp
- Compileroptionen: -g lässt automatische globale Variablen zu (zur
Bob+1-Kompatibilität)
- Move-Semantik für moderne Compiler
- 64-Bit-Version (erfordert mindestens expliziten Adresstyp, außerdem 64Bit-Int, Beachtung
der long-Unterschiede zwischen GCC/MSVC)
- Globale Operatordefinitionen: Alle (überladbaren) Operatoren werden als normale
Funktionen implementiert, wie macht man dann die Unterscheidung bei Operatoren für
Objekte? Oder: es können Funktionen installiert werden, die die Standardoperatoren
ersetzen. Wie geht das effizient und wie kommt man wieder an die Originalimplementierung
heran?
- DOS-kompatible Grafikfunktionen auch unter Windows --> Konsole verbiegen
- Kindprozesse: spawn (DOS/Win), shell->DOS: system, Win->ShellExecute oder
exec
--> system und spawn für DOS/Win (nicht WinCE)