Kategorien
C++Programmierung

C++ as a standalone script?

Seit meinen ersten Tagen als C++ Entwickler wollte ich C++ Code-Schnipsel als eigenständige Skriptdateien verwenden.

So als wenn man folgenden C++20 Code-Schnipsel (siehe mein vorherigen Blog Post) in einer Datei schreibt und dann einfach ausführt:

auto value( auto v ) 
{
	// unnamed concept in constexpr if branch!
	if constexpr ( requires { v.value; } ) {    // if type of v has a .value use this branch.
       return v.value;
    } else {
        return v;
    }
}

long long x = 33;
value( x );

Aber wie kann man dies möglich machen??

Einen Interpreter zu entwickeln, welcher den kompletten Satz der C++ Sprache und ihre Features unterstützt, ist nicht wirklich eine Option für einen einzelnen Entwickler und auch nicht für eine kleinere Gruppe von Entwicklern.

Wie könnte man es stattdessen erreichen?

Die Skripthost Anwendung, welche das Skript ausführt, könnte die Skriptdatei „on-the-fly“ mit einem regulären Compiler kompilieren und das erstellte Binary unverzüglich ausführen und dann das Ergebnis auffangen und zurückgeben.

Aber ein C++ Programm benötigt immer eine main Funktion als Einstiegspunkt!

Eine Idee wäre es den Skript Code in eine temporäre Skeleton Datei einzufügen, welche die main Funktion beinhaltet, und diese dann den Code ausführt.

Aber dabei gibt es viele Limitierungen und Fallstricke. Zum Beispiel ist es unmöglich eine Funktion innerhalb der main Funktion zu definieren (genauer: innerhalb jeder beliebigen Funktion).

Also, mit dem Code Beispiel von oben muss die Funktionsdefinition außerhalb der main Funktion erfolgen, der Funktionsaufruf jedoch muss innerhalb der main Funktion geschehen. Hingegen kann die Variable x sowohl innerhalb als auch außerhalb der main Funktion erfolgen, wobei beide Varianten zu einer unterschiedlichen Lebenszeit und Sichtbarkeit führen.

Das Ergebnis des Code-Einfügens könnte dann so aussehen:

auto value( auto v ) 
{
	// unamed concept in constexpr if branch!!!
	if constexpr ( requires { v.value; } ) {    // if type of v has a .value use this branch.
       return v.value;
    } else {
        return v;
    }
}

long long x = 33; // could be here as a GLOBAL variable.
int main()
{
    long long x = 33; // or here as a LOCAL variable.
    return value( x ); // please note the prepended 'return'
}

Kann dies erreicht werden und wie?

Ok, bevor zu lange darüber nachgedacht wird, schauen wir uns besser erstmal das nächste Skriptbeispiel an …

int a = 2;
int foo( int x ) // must be placed outside main
{
    return x * a; // using 'a', so 'a' must be global
}

// a freestanding if-statement is not possible!
// so it must be placed in main
if( a <= 2 ) { 
    a += 2; // NOTE: changing 'a'
}

int b = foo( 4 ); // foo uses 'a'!
// if 'b' is outside main (global), then this line gets invoked 
// before the if-statement above is executed and thus 'b' will
// have a different value than expected!

void bar( int y ) // must be placed outside main
{
    //using 'b' in function, so 'b' must be global.
    printf( "y * b = %d", y * b ); 
}

bar( 3 ); // inside main
//when invoke the script shall print "y * b = 48"

Sogar mit diesem kurzen (zwar sinnlosen, aber gültigen!) Code-Schnipsel kann man bereits eine Vorstellung darüber bekommen wie schwierig und sogar unmöglich(?) diese Aufgabe werden könnte.
Man muss nicht nur den Code zunächst parsen und verstehen um entscheiden zu können, ob ein Block/Statement innerhalb oder außerhalb der main Funktion platziert werden muss. Nein, es können Entscheidungen nötig sein, die von anderen Blöcken/Statements abhängen!
Und schließlich kommt es noch schlimmer. Wie man oben sehen kann, muss das If-statement (Zeile 9) vor der Zuweisung von ‚b‘ (Zeile 13) ausgeführt werden (weil es ‚a‘ verändern könnte, von dem ‚b‘ abhängt). Jedoch muss das If-Statement innerhalb der main Funktion platziert werden, während gleichzeitig ‚b‘ außerhalb von main platziert werden muss (weil es für die Funktion bar sichtbar sein muss), was dann zu einer verkehrten Ausführungsreihenfolge führt.

Daraus folgt, dass der bisherige Ansatz bei diesem Beispiel nicht funktioniert.

An dieser Stelle habe ich aufgehört diesen Ansatz weiter zu verfolgen.

Die einzige Idee, welche mir noch eingefallen ist, wäre einen Teil von LLVM (das Frontend) zu verwenden um dann im erzeugten Zwischencode herumzurühren bis es ausführbar wird und dann mit LLVM ein Executable zu erhalten.

Aber dafür habe ich momentan noch zu wenig Wissen über die Interna von LLVM. Also habe ich diese Aufgabe erstmal als „kleine Hausaufgabe für zwischendurch“ auf später nach hinten verschoben… 😉

Während meiner Recherche hierzu entstand immer mehr die Idee zu TeaScript und ich fing an mich immer stärker auf dieses neue Projekt zu konzentrieren.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert