TeaScript Overview and Highlights

Overview

(Don’t miss the “In a Nutshell” and “Use case example” section on the start page.)

TeaScript is an embeddable and standalone Multi-Paradigm script language with modern programming features close to C++ syntax but easier to use.

TeaScript can be used

  • to extend C++ Applications with TeaScript dynamically during runtime via the TeaScript C++ Library.
    Once your application has customization points for TeaScript each installation/distribution can be extended/modified without expensive re-compile and deployment procedure with arbitrary and individual functionality (see Use Case Examples).
  • for doing arbitrary tasks by executing standalone TeaScript files via the TeaScript Host Application.

NOTE: Everything from the Highlights is available already and usable right out of the box in the latest Release of TeaScript
☛☛☛ Download is available here. ☚☚☚

Design Principles / Main Goals

The primary goals for TeaScript are

★ provide the best possible easy-to-use and modern API experience for be embeddable and usable in C++ Applications.
★ make modern programming features easily available in an overall fitting language design.
★ be usable in nearly all kind of C++ applications.
★ be a possible replacement for the most of Bash/Windows scripting tasks and be usable for small to medium sized standalone script applications.

The next upcoming Releases will also focus on speedup the execution performance of the scripts. One key feature (beside others) for this will be the integrated ‘Tea StackVM’ (see Teaser at the end of this article).
TeaScript files will be automatically compiled under the hood to the instructions of the ‘Tea StackVM’. This virtual stack machine will then execute the instructions of the script for a higher execution performance.
Actually TeaScript is targeting to be faster than ChaiScript in the majority of disciplines.
A first benchmark was showing very promising results for the performance of TeaScript already:
Benchmark calculating Fibonacci TeaScript VS ChaiScript.

In the meanwhile a second benchmark was done by filling a 32 bit RGBA image buffer pxel by pixel. Have a look who is the winner of this new benchmark:
Benchmark filling image buffer TeaScript VS ChaiScript.

Key Points C++ Library

  • C++ header only Library – programmed in modern C++20.
    • Utilizing the power of the C++ language / C++ standard library internally.
  • No external dependencies possible – include one header and compile.
  • Modern C++ API – easy to use and easy to integrate.
    • Only one include and one to two source lines needed to run a script.
    • Highly customizable in many aspects.
  • Bidirectional interchange and interoperability between the script and the C++ host program (see here also):
    • Strings are std::string, Vectors are std::vector, etc. No conversion required, supported types are directly usable in the script and C++.
    • Access script variables and add variables to the script from C++.
    • Add C++ Callback functions callable from the script for the performance critical parts and/or for use all features of your C++ classes and modules.
    • Pass your private types and contexts as passthrough data through any script layer and use it in callback functions.
  • Fine grained tuning and configuration of the integrated Core Library features/functions for the executed scripts.
  • Optional features – which utilizing the power of other well known C++ Libraries like libfmt.
  • Line-By-Line capable parser.
    • Partial evaluation of partly parsed code.
  • Save the long lasting and expensive re-compile and rollout process of a mature C++ application with customization points for TeaScript code as a dynamic extension at runtime.

Key Points TeaScript Language

  • Host Application with interactive shell, pretty error print and more for execute standalone TeaScript files.
    • portable version – no installation required!
    • Analyze, Test and Debugging capabilities are on board.
  • Full range of Unicode. Strings are always UTF-8.
  • Close to C++ syntax but with some tweaks (see also FAQ), like:
    • No semicolon ; to end a statement (can be easily forgotten).
    • == is equality operator, := is assigning operator, = is syntax error. This prevents the common assign by accident bug where one = was forgotten to type.
    • {} for if-statements and loop bodies are required and not optional for bodies consisting only of one line/statement. This avoids the common statement does not belong to loop/if-statement bug.
    • and some more…
  • Easy to program – ideal for quick solutions – good readability.
  • RAII / RC memory management – No GC!
  • Multi-Paradigm language, including procedural and Functional programming (Lambdas + Higher Order Functions) and generic programming.
  • General purpose integrated Core Library for console IO, file IO, network IO, time measurement, random numbers, string manipulation and many more (Network IO will be added in a future release)

For later versions it is planned to also include:

★ an integrated virtual stack machine (see Teaser at the end of this article).
★ integrated single step debugging capabilities.
suspendable execution of the script (pause and continue).
★ Built-in multi-threaded syntax and asynchronous execution
coroutines

Additional information

Further information is available on the pages for

“Hello World” as standalone TeaScript

In TeaScript the most famous “Hello World” example is just a one liner!

println( "Hello World from TeaScript!" )

Just copy and paste the line into a helloworld.tea file and execute it. Thats all, nothing more to be done!

More TeaScript examples

More TeaScript code examples are provided

Basic C++ Example

A very simple C++ program using TeaScript as an introduction example may look like this:

#include "teascript/Engine.hpp"

int main()
{
    teascript::Engine  engine;
    std::cout << engine.ExecuteCode( "2 + 3 * 2" ).GetAsInteger() << std::endl;
    return EXIT_SUCCESS;
}

The program prints 8 on the screen and exits.
At line 6 the TeaScript code “2 + 3 * 2” is executed with the TeaScript engine, the result obtained and put to std::cout.

Now in the next example we source the TeaScript code out to a separate TeaScript file and then run it again:

/*file: custom.tea*/
2 + 3 * 2
/*file: main.cpp*/
#include "teascript/Engine.hpp"

int main()
{
    teascript::Engine  engine;
    std::cout << engine.ExecuteScript( std::filesystem::path("/path/to/custom.tea") ).GetAsInteger() << std::endl;
    return EXIT_SUCCESS;
}

The program output is still the same as before. Print 8 and exit.
But now in line 7 is not the TeaScript code itself executed but a custom TeaScript file. With this change a customization point was created in the program. If the content of the “custom.tea” file is changed, the already compiled (no re-compile is needed!) program will execute the changed TeaScript code as well.

This custom.tea file can be also executed standalone, either by invoking the TeaScript Host application with the file as program argument or by executing the custom.tea file directly.

Invoking a TeaScript file as standalone with the TeaScript Host Application.
Invoking a TeaScript file as standalone with the TeaScript Host Application.

Also, you can use the interactive shell of the TeaScript Host Application to execute TeaScript code. (The interactive shell is launched when the TeaScript Host is started without program arguments.)

Showing interactive shell of the TeaScript Host Application.

Advanced C++ Example

This example illustrates how easy it is to use user-defined C++ functions from within the script code.

// TeaScript includes.
#include "teascript/Engine.hpp"


// this is our simple callback function which we will call from TeaScript code.
// The callback function signature is always ValueObject (*)( Context & )
teascript::ValueObject user_callback( teascript::Context &rContext )
{
    // look up variable 'some_var'
    auto const val = rContext.FindValueObject( "some_var" );

    // print a message and the value of 'some_var'
    std::cout << "Hello from user_callback! some_var = " << val.PrintValue() << std::endl;

    // return 'nothing' aka NaV (Not A Value).
    return {};
}

// This test code will register a C++ callback function and then executes a script
// which will call it.

// somewhere in the code, e.g. in main() ...

// create the TeaScript default engine.
teascript::Engine  engine;

// register a function as a callback. can use arbitrary names.
// PRO TIPP: use a capturing lambda or std::bind to carry any user context with you.
engine.RegisterUserCallback( "call_me", user_callback );

// execute the script which will create variable 'some_var' and then call our callback function.
engine.ExecuteCode( "const some_var := \"Hello!\"\n"
                    "if( is_defined call_me and call_me is Function ) { // safety checks! \n"
                    "    call_me( )\n"
                    "}\n"                 
                  );

More examples for the C++ API usage is included in the Demo App in the TeaScript C++ Library download bundle (or browsable on Github).

Use Case Examples

The first use case architecture / scenario is probably the most obvious one:

1.)
TeaScript as plug in / enriched dynamic configuration file / custom scripting at customization points.

Your C++ Application may invoke a custom provided script file at a specific stage for doing something else instead of the default action.
This might be a more complex and dynamic configuration at startup which cannot be fulfilled by a classical static data config file anymore.
Or it might be a specific action which differs from the built-in default action if a TeaScript file is provided.
The most generalized version of this might be a plug-in folder where plug-ins for your application can be placed.
Then the app and/or the user decides when and which plugin shall be executed / used.

2.)
TeaScript in the middle layer as orchestrator or execution layer.

Your Application is roughly divided in 3 architectural layers:
The upper layer sets up everything and controls the application (C++).
In the lower layer are the building blocks of types and functions doing the “hard work” (C++).
The middle layer is using and invoking the types and functions provided in the lower layer (TeaScript).

In this scenario the upper layer sets up everything and then executes (chosen by the app or user) a TeaScript file as the middle layer.
The TeaScript in the middle layer decides how and which parts of the lower layer are used.
With that the TeaScript can either act as an orchestrator or it assembles a specific usage of the lower layer functions for providing a new functionality with the building blocks of the lower layer.

3.)
TeaScript as scriptable user content.

The core application is provided by you in C++ but the user (or a third party) is responsible to provide a concrete usage or implementation of their own wish via TeaScript.

A very concrete example “just for fun” would be a “Bot Fighting Simulation”.
The engine which defines the rules and running the simulation is programmed in C++.
But each bot for the simulation is programmed in one TeaScript file.
So, all users can program their own bot(s) and then let them fight in the simulation for see which bot has the better strategy for winning.
With that arbitrary and user defined bots can be created while the simulation engine stays untouched.

There are a lot of more use case examples and possible architectures and also a mix between them imaginable.

Highlights

Here follows a listing of the most impressive highlights of TeaScript. Additional information can be found in the TeaScript Language Documentation as well as in the release articles for tutorial-like introduction of new features.

Bidirectional interoperability

Because of the inner C++ nature TeaScript provides a great interoperability between the host application and the script code compared to other scripting library solutions.

Consider this basic example for illustration:

1) Create a String in TeaScript

def str := "Hello"

2) Access and direct manipulate the String as std::string in C++

// GetValue<T>() returns a reference to the internal std::string object/instance!
// There is not any conversion involved!
engine.GetVar( "str" ).GetValue<std::string>() += " World!";

// Of course, you could invoke any method of std::string or use any function 
// which accept a std::string as well to do what ever you want to do with the String.

3) Use the (modified) String in TeaScript code

println( str )   // will print "Hello World!"

This kind of interoperability is not possible in other scripting solutions like Python, JavaScript or Lua since those are not programmed in C++ nor offer a C++ API.

This is not only possible for std::string but for all (internally used) C++ types regardless if they are from the standard library or are shipped with TeaScript. E.g., you could use the std::filesystem::directory_iterator handle from the TeaScript direntry Tuple in your C++ code if you need and want to.
Also, you can direct access a Buffer in TeaScript as a std::vector<unsigned char> for get the most possible optimization and performance at C++ level.

The bidirectional interoperability is not limited to variable access.
You can provide your own C++ functions and methods callable from the script, add your arbitrary contexts and/or use any of your data as passthrough data type.
An overview and usage examples are provided in the teascript_demo.cpp.

Auto number to string conversion

In TeaScript numbers will be automatically converted to a string if string concatenation is done or inside in-string evaluation.

def mystring := "We have the year " % 2022  // auto to string conversion of 2022. Final string: "We have the year 2022"

def age := 42
println( "Peter is " % age % " years old." ) // will print "Peter is 42 years old." on the screen.

// can just pass a number to the print function
println( 123 ) // output: 123

Auto string to number conversion

In TeaScript strings will be automatically converted to numbers (to i64) when used in arithmetic operations. The strings may contain arbitrary more data after the number. That data will be ignored.

def msg := "100 files found."  // Some string (maybe returned by a remote database lookup)

def total := 250  // some number

def files_left := total - msg    // auto to number conversion of msg. result: 150

// this works also if only strings are involved.
def total_files := "350 total files."

def res := total_files - msg   // res will be 250

In-string evaluation

TeaScript supports in-string evaluation. Expressions inside %() will be evaluated and placed inside the string. For details see the docu section In-String evaluation.

def age := 42
def year := 2022
println( "Thomas is %(age) years old and born in %(year - age)." ) // output: Thomas is 42 years old and born in 1980.
 
def name := "Thomas"
// have some functions 'getageof' and 'currentyear'...
println( "%(name) is %(getageof(name)) years old and born in %(currentyear() - getageof(name))." ) 
 
// assuming getageof returns 42 and currentyear 2022 the output will be:
// Thomas is 42 years old and born in 1980.

String format

You can use the format function to format strings with the well known curly bracket syntax {} as in libfmt and std::format (C++20) (which is based on libfmt as well).
Everything what is possible with the feature set from libfmt format.h for fmt::vformat is usable in TeaScript as well except ‘named arguments’ and that the usable types are limited to teascript::ValueObject for Bool, i64, f64 and String. Everything else will be converted to String. Non-printable types cannot be used.

Please, read the docu of libfmt for all possible format options and features.

// will produce the string "Hello World from TeaScript!"
format( "{2} {1} from {0}!", "TeaScript", "World", "Hello" )
 
// will produce a string with 0 as filler for a 3 column wide string.
format( "{:03}",  1 )  // "001"
format( "{:03}", 12 )  // "012"
 
// will produce a string with 3 digits after the dot.
format( "{:.3f}", PI )  // "3.142"

See format_string.tea for another example.

No dangling references or pointers!

In TeaScript dangling references or dangling pointers are impossible and non existent! Instead you have the choice between copy assign and shared assign, where the latter is like an automatic reference counting shared pointer. See https://tea-age.solutions/teascript/teascript-language-documentation/#copy_assign_vs_shared_assign_operators

def a := "Hello!"    // some string
def b := a           // copy assign, value of a is copied to b, a and b have distinct values (but with identical (copied) content)
a := "Foo Bar"       // a is now "Foo Bar", b is still "Hello"

// The above is just as usual, now comes the shared assign:

b @= a               // shared assign with @=, now a shares it's value with b (a and b pointing to the same value, which is "Foo Bar").

a := "1234"          // now a _AND_ b are "1234"

println( b )         // will print "1234"

// The value of a and b exists until both a and b are gone out of scope / undefined.

// You can query if 2 variables using the same value with @@
if( a @@ b ) {
    println( "a is shared with b" ) // will be printed.
}

Assigning of blocks

In TeaScript every block (not only functions!) returns a value. Thus, it can be used as an expression and assigned to variables.

// can assign the result of an if-statement, a will be 123.
def a := if( true ) { 123 } else { 456 }
def b := 3

// ad-hoc swapping the values of a and b
a := { def tmp := b, b := a, tmp } // tmp is implicit returned by the block and assigned to a
// now a is 3, b is 123 and tmp is not defined.


// result of a loop can be direct assigned to a variable.
// illustrating this by computing the gcd (greatest common divisor) with a loop and store the result directly in a variable:
def x1 := 48
def x2 := 18
def gcd := repeat {         // assign the result of the repeat loop
    if( x1 == x2 ) {
        stop with x1        // the result of the loop
    } else if( x1 > x2 ) {
        x1 := x1 - x2
    } else /* x2 > x1 */ {
        x2 := x2 - x1
    }
}

println( "The gcd of %(x1) and %(x2) is %(gcd)." ) // output: The gcd of 48 and 18 is 6.

Easy loop control – exit outer loops with one statement!

In TeaScript you can address each loop explicitly to stop or to jump to the start of the loop. No burden with maintaining bool or other flag-like variables and testing with several if-statements all over the code if the current loop shall stop. Just say which loop shall be stopped and in TeaScript it will just happen. 😎

def c := 10
repeat "this" {         // loop is named "this"
    repeat "that" {     // loop is named "that"
        c := c – 1
        if( c > 6 ) {
            loop "that" // loop to the head (start) of the inner loop (the "that"-loop)
        }
        stop "this"     // stop the outer loop (the "this"-loop) directly from within the inner loop with one statement!
    }
    // This code will never be reached!
    c := 0
    stop "this"
}
  
// c will be 6 here.

You may also have a look in the article UnitTesting repeat loops.

Handy forall loop

Use the forall loop to iterate over Tuples or Sequences.

def tup := (5, true, "Hello", 3.14)   // some tuple with mixed types.

forall( idx in tup ) {
    println( tup[idx] )    // each element of the tuple will be printed in one line.
}

// can use arbitrary sequences
forall( n in _seq( 3, 12, 3 ) ) {
    print( n % " " ) // will print "3 6 9 12 "
}

Uniform Definition Syntax

TeaScript aims to provide an Uniform Definition Syntax for everything in the language. With the current feature level of TeaScript it is available for variables, functions, function parameters and Tuples/Named Tuples.

The basic idea is to use the keywords / built-in operators def, const, is_defined and undef together with := and @= for all of these things.

Look at the following examples for illustration:

Variables (the basics)

def a  :=   3    // define a new variable

is_defined  a    // true   (more precise 1, it reports the scope distance of the found variable)

is_defined  b    // false, b is not defined

def b  :=   5    // now b is defined

is_defined  b    // true

undef a          // results to true because a has been undefined

is_defined a     // false now! 

// NOTE: reaching code where a is used would be an eval error!
//       except for is_defined and undef operator.
//       Also, a could be defined again with def or const
//       as a different type.

undef a          // false, but no error. undefining non-existing things is not an error.

Functions

Functions can be defined in 2 ways: The classical way and via Uniform Definition Syntax.

// the 'classical' way of function definition:
func sum( a, b )
{
    a + b
}

// Or instead use the
// Uniform Definition Syntax:
def sum := func ( a, b ) { a + b }  

// Both functions are exactly(!) the same in this case.
// (Note: in real code you must use 2 different names in the script for not run into a redefinition error!)
 
// both are called this way:
sum( 1, 2 )   // result: 3

// both can be undefined, etc. (b/c both are just variables)
undef sum


// with the Uniform Definition Syntax you can use const instead of def:
const squared := func ( x ) { x * x } // Function squared is const. squared cannot be undefined.

// just call it normally
squared( 2 ) // result: 4

// undef squared // This would be an eval error!

def squared_2 := squared // assigning a function
// ...

Function parameter specification

For the specification of function parameters the same rules as for variables apply. Except, that the parameter spec has a built-in short cut b/c it is always clear that it is a definition and an assignment will take place. Given that, the def keyword as well as the assignment operator := can be omitted.

// Uniform Definition Syntax illustrated with the parameter specification of a function:
// normal short hand way, def and := omitted
func f1( a, b ) { a + b } 

// the above is exactly the same as:
func f2( def a :=, def b := ) { a + b }

// if you invoke f2 (or f1) then with, e.g.:
f2( 3, 5 + 7 )
// it will be internally like:
func ( def a := 3, def b := 5 + 7 ) { a + b }

// The Uniform Definition Syntax applies completely.
// For the parameter spec you can do everything as when define a variable.

// Setting default parameters is then straight forward:
func f3( a := 3, b := 8 ) { a + b }

f3()    // a default to 3, b to 8, result: 11
f3( 2 ) // setting a to 3, b default to 8, result 10

// But you could also write:
func f4( const a := 3, const b := 8 ) { a + b } // a and b are const
// or you use shared assign for the parameters
// (see https://tea-age.solutions/teascript/teascript-language-documentation/#copy_assign_vs_shared_assign_operators )

func swap( a @=, b @= ) { /* ... */ } // a and b share the values with the caller.

// And finally, because the "assigning of blocks" feature 
// you can also use an if-statement or any other block for default parameters:

func test( x := if( use_old_default ) { 1 } else { 0 } )
{
    x + x
}

def use_old_default := false

// the default parameter will be evaluated per each call.
println( test() )    // output: 0

use_old_default := true
println( test() )    // output: 2

println( test( 2 ) ) // output: 4

Tuples / Names Tuples

See next section:

C -like structs with Named Tuples

With the help of Named Tuples and the Uniform Definition Syntax you can build C-like structs in TeaScript. (Read more about Tuples and Named Tuples in the news article: Release of TeaScript 0.10.0.)

// first create an empty tuple to start with.
def tup := _tuple_create()   // empty tuple.
 
// add elements via the Uniform Definition Syntax like you define new variables:
def tup.first_name := "Jon"
def tup.last_name  := "Doe"
def tup.age        := 42
def tup.email      := "jon.d@anymail.com"

// access the elements like in C/C++ with the dot operator
tup.first_name     // "Jon"
tup.age            // 42
tup.email          // "jon.d@anymail.com"

// you can test for definition as for variables
is_defined tup.email  // true

// remove (undefine) elements as undefine variables
undef tup.email    // now email is undefined.

is_defined tup.email  // false


// TIPP: use a factory function to build same tuple structures:
func create_person( firstname, lastname, age, email )
{
    def person := _tuple_create()
     
    def person.first_name := firstname
    def person.last_name  := lastname
    def person.age        := age
    def person.email      := email
 
    person
}
 
def peter := create_person( "Peter", "Meyer", 42, "meyer@somemail.com" )
 
println( "%(peter.first_name) %(peter.last_name) is %(peter.age) years old." )   // Peter Meyer is 42 years old.

Easy Functional Programming, Lambdas, Higher Order Functions

In TeaScript Functional Programming, using Lambdas and building Higher Order Functions is easily possible via a simple and straight forward way. It just feels natural and can be done fluently without scratching the head.

Functions can be passed as parameters, returned by a Function or stored in variables via shared assign.

I highly recommend to read the section of the documentation: Functions

// starting with the classical example of the recursive calculation of fibonacci
func fib( x ) {
   if( x == 1 or x == 0 ) {
      x                            // ending the chain...
   } else {
      fib( x - 1 ) + fib( x - 2 )  // recursive call
   }
}

fib( 6 )   // result: 8


// Higher Order Functions: passing functions as parameter
 
def test := func ( f, n1, n2 )
{
    f( n1, n2 )   // calling f. f must be sth. callable, e.g., a function
}

// defining some function returning the sum as i64 ...
def sum := func ( a, b ) { a + b }

// the right hand side of the definition above is also a lambda.
// Lambdas can be invoked directly:
func ( x ) { x * x } ( 2 ) // called with x set to 2, result 4


// using the 'sum' function as parameter
def z1 := test( sum, 9, 1 ) // z1 will be 10 (type i64)
  
// passing a lambda (which is just a function definition without a given name)
def z2 := test( func ( x1, x2 ) { x1 * x2 }, z1, 5 ) // z2 will be 50 (type i64)

// Also interesting: The return type can differ (as well as the types of parameters)

// defining another function (now returning a String)
def make_string := func ( a, b ) 
{
    a % b  // use string concat operator
}

// using that function
def z3 := test( make_string, z1, " is a Number." ) // z3 will be type String with value "10 is a Number."


// functions can be also returned by a function.
 
def getfunc := func ( selected ) 
{
    if( selected ) {
        return sum  // returning function sum (return keyword is optional here)
    } else {
        return func ( a, b ) { a * b }  // returning a new function as lambda (return keyword is optional here)
    }
}
 
getfunc( true ) (2, 3)  // direct call the returned (sum) function. result: 5
 
getfunc( false ) (2, 3) // direct call the returned (lambda) function. result: 6

Strong Type Safety combined with easy generic programming

TeaScript comes with a strong Type Safety but nevertheless it offers a very easy way for generic programming.

A variable gets their type along with its definition and is fixed during the whole lifetime. The rules of the concrete type apply. For the parameters of a function the type is determined per each call (it is always a new variable definition, lifetime ends after the call.). With that generic programming is easily available as all functions are template like.

def a := 1     // a is type i64
a := "Hello"   // ERROR! a is i64, not a String

func test( x ) 
{
    // x := 7 // will only be valid if x is a Number.
    const str := "x as string: " % x
    println( str )
    str
}
 
// test can be invoked with different types as parameter value.
test( true )    // output: x as string: true
test( "Hello" ) // output: x as string: Hello
test( 123 )     // output: x as string: 123


// === different dispatching depending on the type! ===
 
func test2( f, x )
{
    if( f is Function ) { // f can be called
        f( x )  // call f, if f is not a function this line will never be evaluated. So, NO eval error for such cases! :)
    } else if( f is String ) { // f is a String
        f % x // concat it
    } else if( f is Number ) { // f is a Number
        f * x // multiply it.
    } else {
        println( "unsupported type of f!" )
        false
    }
}
 
test2( func (n) { n * n }, 3 )  // result 9
test2( 4, 3 ) // result 12

Types are Values!

In TeaScript types are values. They are stored and available via variables and can be used like other variables. Thus, for example you can pass types as parameters or return types from a function.

def MyBool := Bool   // copy-assign of a type
 
def x := true
 
if( x is MyBool ) {
    println( "x is MyBool" ) // will be printed
}

// can be used for casting as well
def num := 123

def flag := num as MyBool  // flag is Bool (true)


func test( T, x )
{
    if( T is TypeInfo ) {  // T is a type
        if( x is T ) {
            println( "value of x is type T" )
        } else {
            println( "value of x is sth. other than type T" )
        }
    } else {
        println( "T is not a type" )
    }
}
 
test( Bool, true ) // output: value of x is type T
test( Bool, 123  ) // output: value of x is sth. other than type T
test( i64,  123  ) // output: value of x is type T


func selecttype( selected )
{
    if( selected ) {
        Bool
    } else {
        String
    }
}
 
def MyType1 := selecttype( true )  // MyType1 will be Bool
def MyType2 := selecttype( false ) // MyType2 will be String

// use dynamic returned type for casting!
def val := 123   // i64
 
def abc := val as selecttype( false ) // abc is String "123"

Future Outlook

The feature set of TeaScript is still far from complete. The above highlights are just those which are implemented and available already. You can expect a huge amount of more impressive features to come!

A few of them are listed below.

Integrated Stack VM

TeaScript will get its own integrated virtual stack machine.
The script code will be compiled under the hood into instructions for the Tea StackVM and then be executed in it.

This will not only lead to a faster execution, but it will be also the architectural foundation for other features, like single step debugging and suspendable execution possibilities (pause and resume).

Parallel Syntax

(To be honest, this is one of the biggest and most complex future feature and it is (unfortunately) still some 0.x releases away. A lot of pre-work and other features have to be done / implemented first.)

Behind this feature you will find a new way for express parallel / multi-threading execution directly build into the core language syntax of TeaScript. For the time being it cannot be unveiled. Watch out for future blog posts and site updates.

The following picture illustrates roughly the capabilities which will be available with the Parallel Syntax of TeaScript (more details to come…).

Parallel Syntax capabilities illustrated in TeaScript.
Parallel Syntax capabilities illustrated in TeaScript.

Asynchronous Execution / Multi-Threading

TeaScript will become a script language with integrated multi-threading and asynchronous execution support.

Network IO

Of course client / server functionality will be possible with TeaScript right out of the box via the integrated Core Library.

There will be also integrated JSON support.

Pipe Syntax

The famous pipe syntax from the Unix shells will be made available in TeaScript for an advanced functional programming experience.

readdata() | filter( some_predicate ) | sort( rule ) | savetofile( "filename.txt" )

Pattern matching | error handling

You can expect something similar as in Rust / Zig for return type/value dispatching and a kind of unified error handling.

Records / Structs / Classes

There is a well defined feature proposal already for introduce some kind of structs with members and member functions which will go beyond the current ‘Named Tuple’ approach.

This might be extended later to some kind of Classes with interfaces and inheritance.

FAQ

Here are answers of some frequently asked questions – mainly regarding the language design.

Why have a distinct new language?

Here I can only give a short answer. To answer this question I could also write a series of long blog articles instead, because there is potentially so much to say.

On the one hand to just use an 1:1 100% C++ syntax is not an option. Neither from the implementers point of view nor from the users point of view. The C++ syntax and core language properties are a way too complex and not suitable for use in a script language where you need quick and short results and which should also be used by Non-Prorgammers or (average) Programmers who are not familiar with C++. To deal with pointers, references, new/delete/malloc/free, C arrays, etc. is very unhandy and also unsafe for a script language. Furthermore, do you really want first write 500 lines of template meta-programming code in a script file before you invoke the one line of execution? 😉
In C++ as a static strong typed and compiled language this is perfectly fine, but not for quick and dynamic scripting solutions.
Also, with a 1:1 copy it would be impossible to offer new modern programming features which are missing in C++ or are more complex to use.

On the other hand to copy an existing language is also not an option, because some advantages for the usage in C++ are then maybe harder or impossible to achieve. Also, if you start to think about what is required or desired in the language you easily end up with own ideas or dislike existing ones. To use an existing language to start with has also a higher effort because you need to deal with every tiny (or complex) aspect right from the beginning. Last but not least you are bound and limited to exact that language definition, regardless if it fits or is needed.
(And nothing to say about the missing creativity of course…)

To make one example for illustration. TeaScript is inspired by a lot of (new) programming languages like Rust, Carbon, Zig and also Lua.
TeaScript has the index based access for Tuples as present in Rust (tup.0). Also, TeaScript shall get a pattern matching mechanism similar as in Rust. But it is not an option to copy Rust as a complete language because Rust is neither a script language nor suitable as one (borrow checking in a easy-to-use script language?!?!?!)

Furthermore, there is not an existing (and maintained) alternative scripting library which offers a modern and easy-to-use C++ API with a high bidirectional interoperability and rich feature set. See Comparison of C++ scripting libraries.

So, TeaScript has the approach to stay somehow close to C++ syntax but also is different for make the usage more easy and straight forward and also to introduce new modern programming features or offer them in an easier way.

Why no ; at statement end?

TeaScript is a script language and (especially) No-Programmers are mostly annoyed by a ; and they often forget it.
Furthermore, it is logically not required, a statement ends when it ends no matter if there is a semicolon or not.
In contrast to e.g., Lua TeaScript does not allow more than one statement in one line just separated by a single space (very unreadable).
However, you could use the comma , as separator (but it is discouraged to do so in the most cases).

Why have := as assignment?

In C++ you have the very common and famous bug that the programmer forgot to type one = for the equality operator == and the statement is an assignment instead of a comparison. To avoid this bug in TeaScript := is assigment, == is equality and a single = is a syntax error.

Why have the def keyword?

Coming from C++ the most logical would be to have auto as keyword for define a variable with automatic type deduction.
But nobody outside from C++ will ever unterstand this. Especially, because you cannot replace auto (def) with the concrete type in TeaScript as you can do in C++. So, auto does not make any sense in TeaScript.

One other option would be to have no keyword at all. But automatic declaration/definition on the first usage is error prone and confusing. Instead, defining a variable with a type and value is always required prior its first usage, which is more safe and clear.

As an alternative there could be var or let as the keyword instead. var seems to fit good as the opposite to const, but there is no ‘unvar’ for undefine it again. let would have a possible unlet for undefine, but undef is more close and natural. Hence, I decided for def (see also next question).

Why have undef as a keyword?

There are 3 reasons for the existence of undef.

1.)
Scripts are tending to use the global scope more heavily. Therefore, it is more likely that a variable name is in use already but the prior type and value is not needed anymore.
But TeaScript does not allow to just change the type of a variable by assigning to it (error prone!). Instead you must first undef the variable and then define it again. This feature avoids to have names like i, i1, i2, i_extra or sth. similar.
Most likely you don’t need to use undef for this reason, but it is there for have the possibility available.

2.)
Manual cleanup of variables in a controlled way. This can be especially useful for more complex types to invoke their destructor/cleanup routines.

3.)
Manipulate the environment from inside the script, e.g., you can undefine functions and variables for make them unavailable for the rest of the script code. This could be especially useful at the startup of the script.

Why not for and while loops?

The most easy way to add a loop as a starting point in a language is an endless loop. Hence, the repeat loop.
The classical for loop from C is not available because TeaScript has neither increment/decrement operators nor any iterators yet. Also, isn’t the usage of the same loop syntax since decades not a kind of boring already? It is also a bit of outdated and the syntax and usage is the most complex compared to others.

Instead, TeaScript will get a forall loop for iterate over collections and sequences, similar to the range-based for loop from C++11.

Furthermore, there are ideas to optionally extend the repeat loop with a repeat while( condition ) {} and repeat until( condition ) {} respectively. However, it is not planned to add a footer controlled loop at all.

Why have a distinct % string concat operator?

First of all, to make a distinction between arithmetic and string operations is a very common feature in script languages. The automatic string to number conversion and vice versa was mainly inspired by Lua. When there is a distinct (string) concatenation operator this feature can be easily implemented and there is the great advantage that it is always clear what the code does. Also, it is under 100% control of the script code programmer.

If any arithmetic operator like +, -, *, / is in use the operands are always converted to a number, but if the string concatenation operator % is used all operands will be converted to a string.

But instead of use the slightly unreadable and maybe error-prone .. from Lua, TeaScript decided to use the % operator as it was done by the boost format library as well.

Why have this weird @= ?

On the one hand TeaScript has no references and pointers for avoid dangling references or access null-pointers.

On the other hand many other languages introduce possible spooky side effects (Python) if all variables are passed as shared pointer like objects (or as reference by default). In those languages the programmer must take extra steps or some extra syntax for make a deep copy of the object prior passing it around / assign it.

To only pass by copy would be an alternative option but with that the language would be a kind of limited.

Instead, TeaScript makes its inner nature of using shared pointers available to the script programmer. Use := for a deep (distinct) copy (no sharing of the underlying value) and @= for a shallow copy with a shared underlying value.

Contact / Get in touch

If you have any questions, suggestions, feature wishes, bug reports or just some feedback, don’t hesitate to contact me.

contact <the-at-symbol> tea-age.solutions or go to the contact form:

Leave a Reply

Your email address will not be published. Required fields are marked *