Inspiriert von Lua und ChaiScript (siehe Blog Post) aber auch von den vielen Ideen, die mir unabhängig von diesen beiden existierenden Varianten in den Sinn kamen, habe ich eine große Anzahl an Ideen und Features aber auch Design und Architektur Überlegungen entwickelt und erarbeitet.
Danach habe ich andere Programmiersprachen verglichen um zu prüfen, was diese anbieten und adressieren. Interessanterweise habe ich herausgefunden, dass einige meiner Ideen darüber „was in einer modernen Programmiersprache gebraucht wird um die Anforderungen von Entwicklern zu erfüllen“ auch dort in der einen oder anderen Weise adressiert wurde. Dies ist insbesondere bei Rust von Mozilla der Fall (https://doc.rust-lang.org/rust-by-example/index.html) und auch bei der – zu dem Zeitpunkt neu angekündigter – Carbon Programmiersprache von Google (https://github.com/carbon-language/carbon-lang) (beides sind keine Skriptsprachen). Vorher hatte ich mich noch nie stark mit Rust auseinandergesetzt und ich kannte nur ein paar Punkte, wie „const“ als default, aber nachdem ich ein paar mehr Sprachfeatures studiert hatte, realisierte ich, dass die Sprache tatsächlich die Anforderungen von professionellen Entwicklern in ihren Kernsprachfeatures adressiert. (Hierzu kommen bestimmt noch ein paar Blog Posts.)
Es war interessant zu sehen, dass andere und ich zum Teil dieselben Anforderungen und Probleme von professionellen Entwicklern erarbeitet hatten. Dies stimmt mich positiv über die Ziele und Features von TeaScript.
Rust und Carbon gaben mir dann auch noch zusätzliche Anregungen und Ideen.
Später habe ich auch noch Inspirationen insbesondere in Zig und Julia erhalten, welche auch einige sehr interessante Sprachfeatures beinhalten.
Folgend jetzt eine grobe Übersicht zu einigen interessanten Features und Zielen nicht nur zur Skriptsprache selbst, sondern auch zur C++ Bibliothek und der Host-Anwendung.
(UPDATE) Mehr Details und eine Dokumentation gibt es in der Sektion Overview and Highlights und in der TeaScript Language documentation.
★ Einerseits nah an der C++ Syntax bleiben ( {}
für Scope Blöcke), aber andererseits die Benutzung für Nicht-C++-Programmierer vereinfachen. Um nur ein paar Änderungen zu nennen:
- Ein Scope Block für If-Statements und Schleifen-Bodies ist immer erforderlich und nicht optional, wenn der Body nur aus einer Zeile/einem Statement besteht.
- Kein Semikolon
;
um ein Statement abzuschließen (kann einfach vergessen werden). ==
ist Vergleichs-Operator,:=
ist Zuweisungs-Operator,=
ist Syntax-Fehler. Dies verhindert den berühmten „Ausversehen Zuweisungs“-Fehler.- Schlüsselwort ‚
and
‘ und ‚or
‘ sowie ‚not
‘ als logische Operatoren.!
,||
und&&
werden nicht als Operatoren verwendet. - Optional kann man
eq, ne, lt, le, gt, ge
als alternative Vergleichs-Operatoren zu den gewöhnlichen==, !=, <, <=, >, >=
verwenden. repeat
undforall
als Schleifen mitstop
undloop
Statement.
★ Bidirektionale Verwendung und Austausch zwischen dem Skript und dem C++ Host-Programm:
- C++ Funktionen sind vom Skript aufrufbar und Skript Funktionen stehen dem C++ Programm zur Verfügung.
- C++ Variablen können im Skript benutzt werden und umgekehrt.
★ Möglichkeit während der Laufzeit zu prüfen, ob eine Variable oder Funktion definiert ist und verwendet werden kann.
- Möglichkeit die Definition von Variablen und Funktionen zu entfernen und wieder neu zu definieren.
★ Reihenfolge unabhängige (Funktions-) Definition: Man braucht nichts Vorwärts-zu-Deklarieren.
func test()
{
foo() // calling foo which is not defined yet.
return x // return x which is not defined yet.
}
func foo()
{
print( "Hello!" )
}
def x := true
// now call test
test() // OK, everything needed is present.
★ Jeder Block (nicht nur Funktionen!) hat einen Rückgabe-Wert. Das letzte ausgeführte Statement wird implizit zurückgegeben. Innerhalb Funktionen ist optional ein Return-Statement verfügbar. Blocks, If-Statements, etc. können Variablen zugewiesen werden.
def a := if( true ) { 123 } else { 456 } // a will be 123.
def b := 3
// ad hoc swap
a := { def tmp := b
b := a
tmp }
// now a is 3, b is 123 and tmp is not defined.
def x1 := 48
def x2 := 18
// loops can be assigned as well (using the 'with' statement).
def gcd := repeat {
if( x1 == x2 ) {
stop with x1
} else if( x1 > x2 ) {
x1 := x1 - x2
} else /* x2 > x1 */ {
x2 := x2 - x1
}
}
// gcd will be 6
★ Einen Weg anbieten um eine konkrete äußere Schleife mit nur einem Statement von einer beliebigen inneren Schleife zu beenden (siehe TeaScript Dokumentation und Wie ich die Repeat-Schleife von TeaScript in C++ „ge-UnitTest-et“ habe. für weitere Details). Auszug aus dem Repeat-Schleifen UnitTest in TeaScrcipt:
def c := 10
repeat "this" {
repeat "that" {
c := c – 1
if( c > 6 ) {
loop "that" // loop to head of inner loop ("that"-loop)
}
stop "this" // stop the outer loop ("this"-loop) from within the inner loop!
}
// should never be reached.
c := 0
stop "this"
}
// c will be 6 here.
★ Automatische String nach Zahl Konvertierung bei arithmetischen Operationen.
def str := "123"
def num := str - 23 // num will be 100 with type Number
★ Automatische Zahl nach String Konvertierung, wenn String Verkettungen ausgeführt werden. (String Verkettungs-Operator ist %
)
def x := 42
def str := "Peter is " % x % " years old. " // produces the string "Peter is 42 years old."
Das Beispiel oben ist sogar noch einfacher mit „In-String-Evaluation“.
★ „In-String-Evaluation“ (mit %(expr)
im String-Literal)
def x := 42
def str := "Peter is %(x) years old." // produces the string "Peter is 42 years old."
Natürlich kann man auch komplexere Sachen machen, wie eine Funktion aufrufen oder einen komplexen Ausdruck ausführen:
func getageof( name ) {
if( name == "Peter" ) { 42 } else {"unknown age"}
}
def str := "Peter is %(getageof("Peter")) years old." // produces the string "Peter is 42 years old."
★ RAII, als eines der wichtigsten C++ Features (automatischen „Destruktor“ Aufruf (oder mehr generisch eine Aufräum-Funktion) beim Verlassen des aktuellen Scopes). Mehr dazu später.
★ Einen Weg anbieten um typ-sicher zu sein aber trotzdem ein dynamisches Typ-System zur Laufzeit haben. Man muss Variablen erst definieren und zuweisen bevor man sie nutzen kann. Davor sind sie nicht benutzbar. Automatische Definition beim ersten Verwenden wird nicht durchgeführt! Während der Zuweisung wird ein konkreter Typ bestimmt. Der Typ kann nicht geändert werden, während die Variable definiert ist.
x := 1 // ERROR! X is not defined
def x := 1 // x is Number
x := "test" // ERROR! Cannot change type. Avoids accidents.
undef x // x is undefined now. That makes the intention explicit!
def x = "test" // x is String now.
★ Auto als default, optionale explizite Typ-Angabe.
★ Nicht evaluierter Code muss syntaktisch korrekt sein, aber braucht nicht ausführbar sein (siehe nächsten Punkt.).
★ Einen einfachen Weg zur generischen Programmierung anbieten.
func test( x ) // in C++ x would be auto
{
// here x has a concrete type (per call).
print( x ) // tries to_string conversion.
}
test( 22 ) // x is Number, will print 22
test( "Hello" ) // x is String, will print Hello
func test2()
{
if( is_defined foo ) {
// No eval error if foo is not defined.
// The execution is guarded by the prior if statement
foo() // return type and value whatever foo() may return...
} else {
// does not matter what type foo() would return. Here we return a Bool.
false // NOTE: remember the last statement will be returned implicitly.
}
}
def val1 := test2() // val1 will be type Bool with value false.
// defining a function foo
func foo()
{
123
}
// call test2 again
def val2 := test2() // val2 will be type Number with value 123
★ Parallele, multi-threaded und asynchrone Operationen direkt in die Kernsprache eingebaut (Mehr zu diesem Thema später!)
★ Einheitliche Definitions-Syntax! Dieselbe Definitions-Syntax für Variablen aber auch Funktions-Definitionen und structs/records/classes.
// classical way for define a function is available as well
func test( x )
{
x * x
}
// variable definition
def x := 3
// uniform function definition
def squared := func ( x ) { x * x }
def res := test( x ) == squared( x ) // res will be true
// maybe you notice that the definition of squared looks also like a lambda.
// YES, lambdas are available as well. And lambdas assigned to variables are exactly the same as named functions.
// You can also return a function or pass it by value:
def getfunc := func () { return func ( x ) { x * x } } // returns a func, return keyword is optional.
def squared_2 := getfunc()
res := squared_2( x ) == squared( x ) // will be true
func test2( f, x ) // passing a func as parameter
{
print( f( x ) )
}
test2( squared_2, x ) // will print 9
Später mehr zu record/class Definitionen.
★ Skript Code kann an Zeilen-Grenzen geparsed werden (oder sogar Zeichen für Zeichen; an komplett beliebigen Grenzen).
- Partielle Evaluierung von allen bereits geparsten Top-Level AST-Nodes.
★ Serialisierung und Deserialisierung des ASTs (zwecks Caching und Performanz).
★ Modifizierung des ASTs mindestens mit Hilfe der C++ Bibliothek, aber wahrscheinlich auch über das Skript selbst.
★ Möglichkeit die AST-Evaluierung an (nahezu) beliebigen Punkten zu stoppen/pausieren und später fortzusetzen.
★ …
Die Auflistung ist alles andere als komplett. Einige Aspekte sind auch noch nicht final spezifiziert, wie u.a. z.B. eingebaute Tuples und Arrays, Objekt-Lebenszeit + Argument Übergabe, struct/record und class Definitionen, Fehlerbehandlung und Ausnahmebehandlung, …
(UPDATE) Mehr Details und eine Dokumentation gibt es in der Sektion Overview and Highlights und in der TeaScript Language documentation.
Alles von den Highlights und noch mehr kann man mit dem neuestem Release von TeaScript ausprobieren. Hier klicken um zur Downloadseite zu gelangen.