Categories
TeaScript

Release of TeaScript 0.10.0 🌞

TeaScript 0.10.0 was published on the 10th March in 2023 and can be downloaded now.
☛☛☛ Download is available here. ☚☚☚

(or browse the source code of the TeaScript C++ Library on Github)

What is new?

The 0.10.0 release is really huge. It brings a battery of new features. Each of them enables a lot of new possibilities and functionality by its own.

The main features are:

  • Tuples and Named Tuples
  • Passthrough data type
  • fine grained Core Library configuration
  • extended filesystem functions.
  • partial evaluation (experimental via low level API)

The TeaScript C++ Library is dual licensed now.
The Library is 100% Open Source and Free Software as per definition of the Free Software Foundation and licensed under the
GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL-3.0).
If this license does not fit for you, see Product Variants for more information.

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

Tuples and Named Tuples

(TeaScript language feature)

Tuples and Named Tuples are really a big beast in TeaScript. They are highly flexible in its use and can be used also as

  • lists, stacks, mimic C-like structs, dictionaries and more.

This is also combined with the Uniform Definition Syntax of TeaScript (see example below).

Basically, tuples are a collection of 0..N values (side note: In TeaScript types are also values.).
Each value is an element of a tuple at a specific index (index starting from 0).
The type of an element cannot be changed, but the value can be. Also, elements can be added, removed or even moved to another index.
Tuples are created via parenthesis () or with the built-in helper function _tuple_create(), which takes arbitrary amount and types of parameters.
Note: empty parenthesis or with a single expression will not produce a tuple but a single value (or NaV), e.g.: (4) is just the value 4 and not a Tuple.

Here are following some examples for illustrating the most interesting use cases:

(Basic) Tuple

def tup := (3,5,7)    // creating a tuple with 3 elements 3, 5 and 7 (all i64)

// access element by index
tup.0   // is 3
tup.1   // is 5
tup.2   // is 7

// access element with built-in function
println( _tuple_val( tup, 2 ) )   // will print 7


// types can be mixed
def col := (true, "Hello", 3.14)

println( col.1  ) // "Hello"

def x := 1 + col.2   // x == 4.14

// tuples can be printed
println( col )    // out: (true, "Hello", 3.140000)

Named Tuple

An element of a Tuple can (optionally) have a name. If it has one, the Tuple is a Named Tuple. Then the element cannot only be accessed by its index but also by its name.
The most common way to construct Named Tuples is via the Uniform Definition Syntax. With that you can build / mimic C-like structs.

// first create an empty tuple to start with.
def tup := _tuple_create()   // zero args == empty tuple.

// add elements via the Uniform Definition Syntax like you define new variables:
def tup.name := "Peter"
def tup.age  := 42
def tup.flag := true

// (Note: the def operator appends a new named element to the end of the tuple.)

// access is also straight forward, just as in C/C++ via the dot operator:
println( tup.name )   // "Peter"

// access via index still possible:
tup.1    // 42

// access via built-in function:
_tuple_named_val( tup, "flag" )   // true

// 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.

Nested Tuples

Of course you can nest tuples.

def tup := (3, 5, ("hello", "world"), true)

(tup.2).0   // "hello" (parenthesis are actually required for nested index access)

def person := _tuple_create()

def person.name  := "Thomas"

def person.birth := _tuple_create()
def person.birth.year  := 1976
def person.birth.month := 10
def person.birth.day   := 17

person.birth.year  // 1976

Comparison of Tuples

Comparison works for all tuples. The equality operators are checking for same inner types (and element names) and same values. Comparing only the structure can be done with _tuple_same_types().

def tup1 := (1,2,3)
def tup2 := (3,5,7)
def tup3 := (2,true,6)

_tuple_same_types( tup1, tup2 )   // true
_tuple_same_types( tup1, tup3 )   // false

def same := tup1 == tup2 // false
same := tup1 == (1,2,3)  // true

def col1 := _tuple_create()
def col1.abc  := 1
def col1.flag := true

def col2 := _tuple_create()
def col2.xyz := 1
def col2.bit := true

_tuple_same_types( col1, col2 )   // false (different element names)

def sth := _tuple_create()
def sth.abc := 5
def sth.flag := false

_tuple_same_types( col1, sth )   // true (note: the order must also be the same)

Uniform Definition Syntax in all its beauty 🌻.

The Uniform Definition Syntax does not only apply for creating named tuples. Tuples can be modified, either with built-in helper functions or with the Uniform Definition Syntax:

// starting with Named Tuples

def col := _tuple_create()
def col.abc  := 3
def col.flag := true

col.0 // 3 (abc)

// just undefine an element like undefine a variable.
undef col.abc

// element abc is undefined now.
// just check it like checking if a variable / function is defined.
is_defined col.abc    // false

col.0   // true  (index 0 is "flag" now)

// add element like define a new variable.
def col.xyz := 5

is_defined col.xyz   // true
is_defined col.1     // true

col.1    // 5 (xyz)


// same works with index based Tuples as well:
def tup := (1,3,5)

// check for definition / element exists is just normal as with variables
is_defined tup.1   // true

// there is not an element at index 3 yet
if( not is_defined tup.3 ) {
    // so add one.
    // it must be the one past last valid index!
    def tup.3 := 6
}

_tuple_size( tup ) // now 4

// remove an element just like undefine a variable
undef tup.0

_tuple_size( tup ) // now 3

// now tup.0 points to a new value:
tup.0   // 3

Tuples as list

Tuples can be used as lists via the built-in functions _tuple_insert(), _tuple_remove() (or undef), _tuple_append() (or def) and _tuple_swap().

// excerpt from corelibray_test03.tea, showing Bubble Sort of a tuple

// some unsorted tuple... 
def tup := (7,9,3,1,6,2,4,8,0,5)

const size := _tuple_size( tup )

func inc( n @= ) { n := n + 1 }
 
// Bubble Sort
def i := 0
repeat {
    if( i == size ) { stop }
    
    def j := i + 1
    repeat {
        if( j == size ) { stop }
        
        if( _tuple_val( tup, j ) < _tuple_val( tup, i ) ) {
            _tuple_swap( tup, i, j )
        }
            
        inc( j )
    }
        
    inc( i )
}

println( tup )    // (0,1,2,3,4,5,6,7,8,9)

Named Tuple as dictionary

A Named Tuple can be used as a dictionary. Use the _tuple_named_xxx() functions.

def tup := _tuple_create()   // empty tuple

// add key (name) value pairs
_tuple_named_append( tup, "key", 123 )


// add a value where key has whitespace and non ASCII chars.
_tuple_named_append( tup, "₿ ぜ 马 克", 1337 )

// you can use full range of Unicode for the key.
// Unfortunately the syntax highlighter here cannot display emojis :(
// Please, see corelibrary_test03.tea for an example.


// get a value
_tuple_named_val( tup, "₿ ぜ 马 克" )   // 1337


// since access by index is possible, we can iterate over the key-value pairs:
const size := _tuple_size( tup )
def   idx  := 0
repeat {
    if( idx == size ) { stop }
    println( "%(idx): %(_tuple_name_of( tup, idx )) = %(_tuple_val( tup, idx ))" )
    idx := idx + 1
}

// test and remove
if( tuple_contains( tup, "key" ) ) {
    _tuple_named_remove( tup, "key" )  // element with name "key" is gone now.
}

Advanced and misc

Use the tuple as a stack with stack_push()/stack_pop().

Have a look at the battery of helper functions in the Core Library for more possibilities: Core Library documentation

Build your own (double) linked list types with Named Tuples (see double_linked_list.tea demo script).

You can use all kind of values for Tuple elements. Also, Types, Lambdas and Functions. Interesting and generic things are possible with it!

Tuple elements can be const defined as variables can be. Just use const instead of def via the Uniform Definition Syntax.

Use tuple_print( tuple, "tuple", nesting_depth ) to debug print named tuples.

For another example of tuple usage have a look in the tuple_demo.tea script.

Passthrough data type

(TeaScript C++ Library feature)

Use the new Passthrough data type to tunnel your own types and classes through the TeaScript layer of your application. See teascript_demo.cpp for another example.

struct MyDataType 
{
    std::string member1;
    int member2;
};

auto data1 = std::make_shared<MyDataType>( "Something", 123 );
auto data2 = std::make_shared<MyDataType>( "Anything", 789 );

// callback function
teascript::ValueObject data_processing( teascript::Context &rContext )
{
    if( rContext.CurrentParamCount() != 1 ) { // this check could be relaxed also...
        // in case of wrong parameter count, throw eval error with current source code location.
        throw teascript::exception::eval_error( rContext.GetCurrentSourceLocation(), "Wrong amount of parameters! Expecting 1." );
    }

    // get the first param
    auto val = rContext.ConsumeParam();
    // get our data out of it.
    auto sp = std::any_cast<std::shared_ptr<MyDataType> >(val.GetPassthroughData());
    
    // use it...
    std::cout << sp -> member1 << std::endl;
    
    // just return sth ...
    return teascript::ValueObject(true);
}

// somewhere in the code, create TeaScript default engine
teascript::Engine  engine;

// register the processing function as callback. can use arbitrary names.
engine.RegisterUserCallback( "process", data_processing );

// add our data as Passthrough data
engine.AddPassthroughData( "data1", data1 );
engine.AddPassthroughData( "data2", data2 );

// call a script
engine.ExecuteCode( "if( rolldice() > 4 ) { process( data1 ) } else { process( data2 ) }" );

// depending of the rolldice result, either "Something" or "Anything" will be printed.

The Core Library of TeaScript is using the Passthrough data type as well. The directory entry iterating via readdirfirst()/readdirnext() stores its handle to a C++ std::filesystem::directory_iterator this way.

Core Library Configuration

(TeaScript C++ Library feature)

Decide exactly and fine grained which parts or which level of the integrated Core Library shall be loaded into the Engine.
For example, if you don’t want have any file io functionality, you can prevent to load it. Then the TeaScript code is not able to do any file io at all.
Another example is, if you want to provide your own Library and built-in functions for the TeaScript code. Then you can configure to load only core functionality or even only the TeaScript types as a minimal version.

// The TeasScript default engine. This time we configure it to not load any file io and std io functions and use only the util level.
teascript::Engine  engine{teascript::config::no_fileio( teascript::config::no_stdio( teascript::config::util() ) )};

// only core is loaded:
teascript::Engine  engine_core{teascript::config::core() };

// more fine tuning is possible.
// PRO TIPP: Many Core Library functions without starting underscore _ are mutable and can be undefined or overwritten!

See more config possibilities at top of CoreLibrary.hpp.

Extended filesystem functionality

(TeaScript language feature)

Iterate through directories via raddirfirst()/readdirnext() (see dir.tea example script).

Use more of the new filesystem functions of the Core Library. See Core Library documentation.

Highlights are file_copy()/file_copy_newer(), path_delete() and last_modified().

Partial Evaluation

(TeaScript C++ Library feature)

Partial Evaluation of partly parsed code is available as an experimental feature via the low level API. See teascript_demo.cpp for an example.

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. ☚☚☚

Some final notes and outlook

The straight forward development from TeaScript 0.9.0 to 0.10.0 and the good working integration of the Tuple/Named Tuple features show to me (and I count it as a proof), that TeaScript is based on a solid and easy extendable foundation. If you look closer into the source code, you may notice that there are only a few steps to be done for have a vector of teascript::ValueObject. Other things seem to be easy to get integrated as well. That all makes me think, that TeaScript is on a good way and has a lot of potential for future developments.

So, what are the next steps?

There will be Arrays and Vectors. The difference to Tuples is, that every element must be of the same Type. This will also count for the inner type of the teascript::ValueObject. The language will enforce this rule.
Furthermore, they will be present as plain std::vector with no noise around it. But here I have an additional goal:
I will try that the primitive types like i64, f64 and even String will be present as std::vector< PrimitiveType > and NOT as std::vector< teascript::ValueObject>. With this, I will enable to get the maximum performance and most optimal memory layout for the C++ level / Library.
But this will come with one major drawback: you cannot “share assign” an element of a vector with primitive types since this can only work with teascript::ValueObject. Because of that I think, I will make a difference between Array and Vector. Vectors will contain always ValueObjects while an Array can be std::vector< PrimitiveType >.

Of course, there will be a subscript operator [] for access elements by index. The subscript operator will be added to Tuples and Named Tuples as well.

Probably together with the Vector I will add an u8 type for low level byte work as well as a forall loop to iterate over collections/sequences.

One big thing for one of the next release I have in mind, is integrated TOML support. My idea is to read TOML files 1:1 into Named Tuples of TeaScript. With that the TOML structure is accessible with the dot operator . like a struct in C/C++. How does it sound for you?

I will be happy for every feedback to the current 0.10.0 Release and TeaScript in general! Also I will be happy for any suggestions, feature wishes, bug reports and other comments. I hope, you can enjoy TeaScript and it is a value for you / for your needs / for your application.

Unfortunately,

at the moment it seems to be that I will run out of resources and time, so that I most likely have to work for/on other projects for be able to invest more in TeaScript again. So, probably the next release will get a delay of an unknown duration. I hope, it will not be very long since I really like this project.

Leave a Reply

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