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.
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.
All values can be tested as boolean values in the context of a conditional statement:
null value always evaluates to false.false if empty,
true otherwise.true.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.
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.
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[x] is valid for an x of
type riddle::Val, the operator uses that instead.nth element from the beginning is accessed, otherwise the 1 -
nth element from the end is accessed.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.
All types are printable; usefulness of the printed data might vary though:
Null value is printed as null.Bool values are printed as either true or false.std::integral or std::floating_point is printed as a
number.std::ranges::forward_range…char, it is printed as a double-quoted character string
such as "", "foo" or "\n". This includes the built-in String
type.key:value pairs. This includes the built-in
Map type.List type.<Type>.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.
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