Inspired from Lua and ChaiScript (see blog post) but also from so many ideas which came to my mind unrelated to these 2 existing variants, I developed and created a huge number of ideas and features as well as design and architectural aspects for TeaScript.
After that I was comparing other programming languages to check what they are offering and addressing. Interestingly I found out, that some of my ideas about “what is needed in a modern programming language for fulfill the needs of developers” are also addressed there in some way. This is especially the case for Rust (https://doc.rust-lang.org/rust-by-example/index.html) by Mozilla and also for the – to that time newly announced – Carbon programming language (https://github.com/carbon-language/carbon-lang) by Google (Both of them are not script languages.). Before I did not dive deep into Rust and I only knew some basics, like it has ‘const’ as default, but after studying some more language features, I realized that the language is really addressing the needs of professional developers by its core language features. (You can expect some upcoming Blog posts related to this.)
It was interesting to see, that others and I partly had figured out the same needs and problems of professional programmers. That makes me looking forward positively for the goals and features of TeaScript.
Rust and Carbon also provided some new ideas and aspects that influenced me.
Also, later on, I found some additional inspiration especially in Zig and Julia which both offer some very interesting language features as well.
And now follows a rough overview of some interesting features and goals not only related to the script language itself but also to the C++ library and script host application.
(UPDATE) More details and a language documentation is available in the section Overview and highlights as well as in the TeaScript language documentation.
★ Be close to C++ syntax (e.g., using {}
for scoped blocks, etc.) on the one hand, but on the other hand make it easy for non-C++ programmers as well. To just mention a few changes:
- A scoped block for if-statements and loop bodies is required and not optional for bodies consisting only of one line/statement.
- No semicolon
;
to end a statement (can be easily forgotten). ==
is equality operator,:=
is assigning operator,=
is syntax error. This prohibits the common assign by accident bug where one=
was forgotten to type.- Keyword ‘
and
’ and ‘or
’ as binary logical operators, ‘not
’ as the unary logical not. In contrast to this!
,||
and&&
are not used as operators. - Optionally use
eq, ne, lt, le, gt, ge
as an alternative for the comparison / equality operators==, !=, <, <=, >, >=
respectively. repeat
andforall
as loops withstop
andloop
statements
★ Bidirectional use and interchange between the script and the C++ host program:
- make C++ functions callable from inside the script and make functions defined in the script callable from the C++ program.
- Make C++ variables usable in the script and vice versa.
★ Possibility to check if variables and functions are defined and available for use during runtime.
- Possibility of undefine and re-define variables and functions.
★ Order independent (function) definition: Don’t need to forward declare anything.
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.
★ Every block (not only a function!) returns a value. The last executed statement is returned implicit. In functions an optional return statement is available. Blocks, if-statements, etc. can be assigned to variables.
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
★ Have a way to stop a concrete specified outer loop from inside an arbitrary inner loop with one statement (see TeaScript documentation and How did I “UnitTest” the repeat loop of TeaScript in C++ for more details). Excerpt from the repeat-loop UnitTest in TeaScript:
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.
★ Automatic string to number conversion when using arithmetic operators.
def str := "123"
def num := str - 23 // num will be 100 with type Number
★ Automatic number to string conversion when doing string concatenation. (Concatenation operator is %
)
def x := 42
def str := "Peter is " % x % " years old. " // produces the string "Peter is 42 years old."
This example above is even easier with “in-string” evaluation.
★ In-String evaluation (via %(expr)
inside the string literal):
def x := 42
def str := "Peter is %(x) years old." // produces the string "Peter is 42 years old."
Of course you can do more complex things like call a function or put a complex expression:
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, as one of the most important C++ features (automatic calling a “destructor” (or more general a cleanup function) when leaving the scope). More to be come soon!
★ Provide a way to be type safe but offer a dynamical type system at runtime.
You must define and assign a variable first. Before it is not usable. Automatic definition when use the first time will not be done! During the assignment a concrete type is determined. The type cannot be changed during the variable is defined.
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 type by default, optionally explicit specify a concrete type.
★ Unevaluated code needs to be syntactical correct but does not need to be executable (see next point).
★ Provide an easy way for generic programming.
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
★ Parallel, multi-threaded and async operations integrated directly into the core language (more to come later to this topic!).
★ Uniform definition syntax! Using same definition syntax not only for variables but also for functions and 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
Await more for record/class definitions…
★ Script code can be parsed at line boundaries (or maybe even char by char; at completely arbitrary boundaries).
- Partial evaluation of all parsed top-level AST-Nodes.
★ Serialization and deserialization of the parsed AST, for e.g., caching/performance.
★ Modifying the AST at least from the C++ Library, but probably also from inside the script itself.
★ Possibility to stop/pause the AST evaluation at (nearly) any time and any point and continue it later.
★ …
The list is far away from completeness. Some aspects are also not finally specified yet, including but not limited to: built-in tuples and arrays, object lifetime + argument passing, struct/record and class definitions, error handling and exception handling, …
(UPDATE) More details and a language documentation is available in the section Overview and highlights as well as in the TeaScript language documentation.
Most from here and everything from the highlights and more you can try by your self with the newest release of TeaScript. Click here for visit the download page.