Riddle Types

All Riddle values are first C++ values; the operations that can be performed on them are inherited from what the C++ value can, in a mostly straightforward way.

Native Types

The Null type is represented by the single stateless object null.

The Bool type, with underlying type bool, is represented by the values true and false.

The Int type, with underlying type long, is represented by base ten numbers with no decimal part or exponent,

The Float type, with underlying type double, is represented by base ten numbers with a decimal part, exponent or both.

The String type, with underlying type std::string, is represented by double-quote-enclosed character sequences.

The List type, with underlying type std::vector<riddle::Val>, is represented by bracket-enclosed, comma-separated value lists such as [], [1], [1, 2, 3].

The Map type, with underlying type std::map<riddle::Val,riddle::Val>, is represented by curly-brace-enclosed, comma-separated list of key:value pairs such as {}, {1:2}, {"pi":3.14159, "e":2.71828}.

Any other C++ value can appear in a Riddle script by being exported to a scope, passed as an argument, or returned by a function.

Boolean Testing

All values can be tested as boolean values in the context of a conditional statement:

Comparison

All objects can be compared with the ==, !=, <, >, <=, and >= operators.

Note that comparing the addresses means that there will always be a valid comparison operator implementation and it will return the same values in the same run, but the comparison itself might return different values in different runs when objects were allocated in different places.

Arithmetic & Bitwise

The basic binary arithmetic operators (+, -, *, /) are supported in a case-by-case basis:

Additional binary operators (%, <<, >>, &, |, ^) are implemented by attempting to cast the right-side operand to an integer,

Unary operators (~, -) are implemented if the type supports them.

Sequences

Sequence types (those satisfying std::ranges::forward_range) implement additional functions:

The size() operator that returns the size of the range. Note that this is implemented on the C++ side but no such operator is provided by default in the global scope.

The subscript operator sequence[n] is generated as follows:

Sequence iteration is also handled automatically; from the point of view of the script, it means any sequence object can be used as the iterable value in a for statement; internally, the next operator is a function that takes a sequence and an iterator, and returns the iterator to the next element, or null if the element is the last one. If the iterator passed to the function is null, it returns the start of the sequence.

Printing

All types are printable; usefulness of the printed data might vary though:

Printing ranges could result in a nasty bit of recursion if an object holds a reference to itself somewhere inside it, so ranges are only fully printed if they are not being printed as part of their parents being printed themselves, otherwise a reduced form that does not show the elements is used.

By default, a type name is obtained by calling typeid(T).name(); in some environments this will result in a more or less mangled name; and in many cases the name will be too elaborate to be useful. The system allows users to provide specializations to the riddle::Type_name struct that give shorter type names for specific types.

The system also allows users to provide specializations to the riddle::Print template to provide custom printing functions to any type.

Functions

By default, Riddle will generate the call operator for any value that can be invoked with arguments of type const Val*, Val*, int and returns Val. The first argument is a pointer to the environment variables in the global scope, the second a pointer to an array of arguments, and the third the number of arguments with which the function is called.

Riddle also provides some facilities to generate an type with that implementation of operator() for any callable object.

Use riddle::make_fun() to generate a callable object that deduces the types and number of arguments from one function:

    struct Foo { int bar; }
    Foo foo(int a) { return Foo(a); }
    auto val = riddle::Val(riddle::make_fun(&foo));

    >>> foo(1)
    <Foo>

Use riddle::make_funs() to generate a callable object that deduces the types and number of arguments from several functions and calls the first one whose arguments match:

    struct Foo { int bar, baz; }
    int foo_1(int a) { return Foo(a, 0); }
    int foo_2(int a, int b) { return Foo(a, b); }
    auto val = riddle::Val(riddle::make_funs(&foo_1, &foo_2));

    >>> foo(1)
    <Foo>
    >>> foo(1,2)
    <Foo>

Use riddle::make_field() to generate a callable object that works as a getter or setter for a field of a C++ object, depending on the number of arguments you call it with:

    auto val = riddle::Val(riddle::make_field(&Foo::bar));

    >>> f = Foo(1)
    <Foo>
    >>> bar(f, 2)
    2
    >>> bar(f)
    2