TeaScript 0.15.0 was published on the 1st September in 2024 and can be downloaded for free:

UPDATE: In the meanwhile a new release with new features has been done. Check out the download page!

All details, features and changes for this new release are following in the blog post below.

The download page with more infos, links and basic instructions:
β˜›β˜›β˜› Download Page. ☚☚☚

Browse the source code of the TeaScript C++ Library on Github.

Are you new to TeaScript? Then you may read here first: Overview and Highlights of TeaScript.

Watch demos, tutorials and more on the YouTube Channel.

What was new?

The previous release blog posts are nice for a tutorial like introduction of the new features and for getting an overview. If you missed some, here is the collection.

What is new?

The TeaScript 0.15.0 release comes with the following new main features:

Web Client / Server Preview

The pre-compiled Windows and Linux packages of the TeaScript Host Application have this feature enabled by default (download links at the top of the page). For the TeaScript C++ Library it is an opt-in feature and must be enabled first (see below).

The web preview module adds functionality for issuing http requests (client) or receive and reply to them (server). Internally TeaScript is utilizing the power of the awesome Boost.Beast and Boost.Asio C++ libraries. (Well, for this preview maybe only 1% of the available power is used already... ;-) )

This feature comes along with automatic enhanced JSON support for payloads with Content-Type "application/json".

You can try it easily by your self with the provided web_client.tea and web_server.tea example script files.

Here is another simple example (error handling omitted):

// issue a http GET request (here to a test page for JSON as payload)
def res := web_get( "headers.jsontest.com" )

// access http status code and its reason (may print "200: OK")
println( res.code % ": " % res.reason )

// print all header entries of the reply
tuple_print( res.header, "header", 1 )

// the above may print:
// [...]
// header.Date: "Sat, 31 Aug 2024 13:34:26 GMT"
// header.Server: "Google Frontend"
// header.Content-Length: "236"


// print the payload of the reply as String
println( res.payload ) // this will print a json formatted string


// if the payload was sent as Content-Type "application/json"
// TeaScript automatically builds a Tuple out of it.

if( is_defined res.json ) { // json Tuple is present
    // we can access all elements/objects directly.
    // In this example the server just echoed the header of
    // the request back as a json formatted string.

    // print the Host value
    println( res.json.Host )
    // print the User-Agent
    println( res.json["User-Agent"] )
    // dot access possible as well!
    println( res.json."User-Agent" )
}

Issuing a http POST is easy as well:

// easily create the json payload
def json_payload := json_make_object( ("name", "John"), ("age", 31) )

// issue the http POST with our json payload (plain String also works as paramter)
def reply := web_post( "postman-echo.com", json_payload, "/post" )

// now we can access the result/reply message as before.

// lets print the whole json object from the reply...
tuple_print( reply.json, "json", 10 )

// may print:
// [...]
// json.args: <Tuple>
// json.data: <Tuple>
// json.data.age: 31
// json.data.name: "John"
// [...]
// json.headers.connection: "close"
// json.headers.content-length: "24"
// json.headers.content-type: "application/json"
// json.headers.host: "postman-echo.com"
// json.headers.user-agent: "TeaScript/0.15.0"
// [...]

For a web server example, please, have a look at web_server.tea.

Howto enable the web preview for the C++ Library

The web preview module is not header only and it has a dependency to Boost.Beast / Boost.Asio. Because of that, the following steps have to be done in order to use it in the TeaScript C++ Library:

You can also watch a short How-To on YouTube: https://youtu.be/SeRO21U1vMk

JSON Support

TeaScript now has build-in JSON support. You can convert a JSON formatted String into a Tuple object and vice versa. The Json can be written to/read from a file as well. Also, TeaScript offers a bunch of utility functions for access and manipulating JSON compatible Tuple objects.

The Json support is enabled by default and is served by the tiny PicoJson library, which is shipped with the TeaScript source.

If you use or want use the TeaScript C++ Library, it is likely that your C++ project is using a different Json library. For that case, you can change the used Json library of TeaScript. This release comes with available Json adapters for nlohmann::json, RapidJSON and Boost.Json.

While the internally used Json Adapter of TeaScript is exactly one and must be chosen at compile time, all available Json adapters can be used at runtime for manually import/export Json objects of the concrete adapter. This is for the unlucky case (but I have seen many already!) that not one Json library is in use but many.

You might have a look at the corelibrary_test05.tea code to see how the JSON support could be used (and how its functionality is tested).

Here is another simple example:

// json formatted array string to Tuple
def json := readjsonstring(  "[1, 2, 3, 4]" )

// use the utility functions for json arrays
println( json_is_array( json ) )     // true
println( json_array_empty( json ) )  // false
println( json_array_size( json ) )   // 4

// access
println( json[1] ) // 2

// manipulate
json_array_append( json, 5 ) // value 5 appended

json_array_remove( json, 1 ) // index 1 removed

// to json formatted string
def str := writejsonstring( json )

println( str )  // "[1,3,4,5]"


// use a more complex json object

const object_str := """"
{
    "name": "John",
    "age": 31,
    "additional": [1, true, "Hello"]
}
""""

def json_object := readjsonstring( object_str )

println( json_object.name ) // "John"

println( json_is_array( json_object.additional ) ) // true

println( "%(json_object.additional.2) World!" )    // "Hello World!"


// create json compatible tuples:
def new_json := json_make_object( ("foo", "bar"), ("myarray", json_make_array(1, "test" ) ) )

// build a json formatted string
def new_str := writejsonstring( new_json ) // "{"foo":"bar","myarray":[1,"test"]}"

There are convenience functions for read/write Json formatted Strings from/to files via readjsonfile() and writejsonfile() respectively. Have a look in the Core Library Documentation for all json utility functions or use :search json from within the REPL of the TeaScript Host Application.

Technical noteworthiness

TeaScript does not have distinct arrays and dictionaries but (only) Tuples which can act as both of them and much more.

If a Tuple shall be used as a Json value and it contains key value pairs then it is considered to be a json object. But if it contains only values, then it is considered to be a json array.

But if a Tuple object is empty, it is not possible to distinguish between an empty json array and an empty json object. For this case TeaScript inserts a marker inside a Tuple which comes from a json import or should be used for a json export and was considered to be an json array.

Because of that it is really important to use the provided utility functions for json arrays. They will take care of the specialties under the hood.

Howto change the Json Adapter

The Json adapters are not header only and have dependencies to other Json C++ libraries. Because of that, the following steps have to be done:

Thats it. After this changes, TeaScript will use nlohmann::json for the Json support.

Complete TOML support

Before, TeaScript was able to read TOML files/strings already. Now it can also write them with writetomlstring() and writetomlfile().

The TOML feature is optional. In order to activate it, you must download the tomlplusplus library and add the include directory to your project. Thats all, TeaScript will automatically detect it.

The same kind of utility functions were added to the TOML support as it was for JSON. Have a look in the Core Library Documentation for all toml utility functions or use :search toml from within the REPL of the TeaScript Host Application.

Please note, the same noteworthiness as for json applies to toml. So, in order to support possibly empty arrays, you must use the corresponding utility functions, like toml_is_array, toml_array_empty, toml_array_size, toml_array_append, toml_array_remove and so on.

Import/Export of C++ Json objects

Within C++ you can import and export C++ Json objects of all available adapters/json libraries.

For the time being you need a valid instance of the corresponding TeaScript Context. If you use the Engine class, the easiest way is to derive from it and use the Context member from there.

The demo app uses this feature in the web preview code. You can view the source of the function here: teascript_demo.cpp webpreview_code().

Here is some little example code using nlohmann::json:

// somewhere in the code where a TeaScript context is available...

// just make some json object
nlohmann::json  value;
value["name"] = "John";
value["myarray"] = { 4, 5, 6 };

// import it
ValueObject  val = teascript::JsonAdapterNlohmann::ToValueObject( context, value );

// add it as variable 'json'. ('context' and the context of the engine should be the same.)
engine.AddSharedValueObject( "json", val );


// export is even more simple

// assuming there is a 'json2' variable which is a json compatible Tuple...
auto const valobj = engine.GetVar( "json2" );

// and export it to nlohmann::json.
nlohmann::json  value2;
teascript::JsonAdapterNlohmann::FromValueObject( valobj, value2 );

Shortcut for creating dictionaries

Tuples with named elements are used as the map/dictionary container in TeaScript. Before you had to use the Uniform Definition Syntax for create them or use the _tuple_named_append/insert functions.

Now, a third way is available, which enables creating dictionaries easily with one call: _tuple_named_create()

This functions excepts an arbitrary amount of parameters. Each parameter must be a tuple with 2 elements. The first element must be always of type String. This will be the key of the entry. The second element can be of any type and will be the value of the entry.

Here is some example code showing the 3 available possibilities:

// old way, use _tuple_named_append
def dic1 := _tuple_create()              // empty tuple to start with.
_tuple_named_append( dic1, "elem1", 1 )  // added "elem1" with value 1
_tuple_named_append( dic1, "elem2", "foo" )  // added "elem2" with value "foo"


// old way (but still great!), the Uniform Definition Syntax.
def dic1.elem3 := true      // added "elem3" with value true
def dic1.baz   := _buf(10)  // added "baz" with Buffer of size 10


// Now we do the same with the new _tuple_named_create()

def dic2 := _tuple_named_create( ("elem1", 1), ("elem2", "foo"), ("elem3", true), ("baz", _buf(10) ) )


println( dic1 == dic2 )   // prints: true

Easy to use C++ LibraryFunction object

If you want to extend TeaScript via C++ functions there were 2 possibilities available.

The first is the high-level API via a UserCallbackFunc and a call to RegisterUserCallback() of the Engine class.

The advantage of this is, a callback can be installed and invoked very easily. The disadvantage is, the parameters will not be passed directly but you must pull it from the Context and manually check for its inner value type.

The other way was to use the low-level API via the LibraryFunction0 to LibraryFunction5, which is also used internally for the CoreLibrary. The usage of this is neither easy nor straightforward. Also, there are some limitations and partly missing features.

Therefore, the TeaScript C++ Library now offers a generic LibraryFunction as a low level alternative for registering a C++ function for be invocable from TeaScript. This new generic variant is more easy to use and comes without the old limitations and missing sub-features.

View the source here: LibraryFunction.hpp.

Here is a small example:

// imagine there is a custom module for TeaScript

// please NOTE: The module interface is considered EXPERIMENTAL
class MyModule : public IModule
{
public:
    // these are the functions of the module....
    static ValueObject Test( Context &rContext, ValueObject const &rRequest );
    static bool Test2( long long x, std::string const & y );
    static bool Test3( Context &rContext, std::string const &x, double a );
    static void Test4();
    static bool Test5();
    static bool Test6( Context &rContext );
    static std::string Test7( long long a );

    void Load( Context &rInto, config::eConfig const, bool const ) override;

    std::string_view GetName() const override
    {
        using namespace std::string_view_literals;
        return "MyModule"sv;
    }
};



// then inside the cpp

void MyModule::Load( Context &rInto, config::eConfig const, bool const )
{
    auto const  cfg = ValueConfig( ValueShared, ValueConst, rInto.GetTypeSystem() );

    // every function can be registered straight forward via the same call!

    {
        auto func = std::make_shared< LibraryFunction<decltype(Test)> >( &Test );
        ValueObject val{std::move( func ), cfg};
        rInto.AddValueObject( "Test", val );
    }
    {
        auto func = std::make_shared< LibraryFunction<decltype(Test2)> >( &Test2 );
        ValueObject val{std::move( func ), cfg};
        rInto.AddValueObject( "Test2", val );
    }
    {
        auto func = std::make_shared< LibraryFunction<decltype(Test3)> >( &Test3 );
        ValueObject val{std::move( func ), cfg};
        rInto.AddValueObject( "Test3", val );
    }
    {
        auto func = std::make_shared< LibraryFunction<decltype(Test4)> >( &Test4 );
        ValueObject val{std::move( func ), cfg};
        rInto.AddValueObject( "Test4", val );
    }
    {
        auto func = std::make_shared< LibraryFunction<decltype(Test5)> >( &Test5 );
        ValueObject val{std::move( func ), cfg};
        rInto.AddValueObject( "Test5", val );
    }
    {
        auto func = std::make_shared< LibraryFunction<decltype(Test6)> >( &Test6 );
        ValueObject val{std::move( func ), cfg};
        rInto.AddValueObject( "Test6", val );
    }
    {
        auto func = std::make_shared< LibraryFunction<decltype(Test7)> >( &Test7 );
        ValueObject val{std::move( func ), cfg};
        rInto.AddValueObject( "Test7", val );
    }
}

Misc

Deprecation

The following deprecated parts have been finally removed from this release:

exit( exit_code ) Use either _exit( Any ) or the new _Exit statement instead. A script will always have a result value (or NaV (Not A Value)).

class Engine HasExitCode()/GetExitCode() and mExitCode A script will always have a result value (or NaV (Not A Value)).

For Visual Studio Users

Depending on your project / code size it might be possible that you must add the /bigobj flag to the β€œAdditional Options” settings.

Unfortunately Microsoft still has not set this as the new default and every now and then a project runs into this annoying issue.

As you can read on StackOverflow the setting can just be activated if the code reaches a specific size.

If you also find it very annoying you might vote or write a comment in the corresponding topic in the Microsoft developer community as I did as well.

Limitations with clang

TeaScript is a C++20 library and relies on C++20 language and standard library features. Unfortunately, as of today clang in combination with libc++ does still not support std::stop_source and std::stop_token which is a C++20 library feature.

If you need the TesScript feature of sending a suspend request to a running TeaScript program inside the Tea StackVM by another thread you cannot use this feature right out of the box.

There are 3 possible solutions beside in don't use this feature at all:

Please, see also the Known_Issues.txt for other known issues on Linux / with clang.

Linux support / g++13

Starting with TeaScript 0.14.0 release all known issues specific for Linux are solved if you use g++13 or newer.

Therefore, I highly recommend to use g++13 (or newer) on Linux.

However, the pre-compiled Linux download bundle of the TeaScript Host Application is still built with g++11 for the best possible platform compatibility.

Please, see also the Known_Issues.txt for the list of known issues.

More Infos

More information about the TeaScript language is available here:

✯ Overview and Highlights
✯ TeaScript language documentation
✯ Core Library documentation

β˜›β˜›β˜› Try and download TeaScript here. ☚☚☚

Outlook

There are some top new features on my list to implement next

From the time perspective, I think, the next release will likely to be possible on end of December 2024 or during January 2025.

I will be extremely happy for any feedback, suggestions, questions and other kind of constructive feedback.

I hope you will enjoy with this TeaScript release. :)