Enums, Namespaces, Destructuring & Friends​

The smaller pieces of the language: enums, namespaces and using, destructuring, the conditional operator and null-safe access. Each works as a C++ developer would guess; the differences are stated.

Enums​

An enum is a type. enum Dir { north, south, east, west } declares Dir and its values, written Dir::south (the dot form Dir.south names the same value).

  • A Dir slot (variable, parameter, return, field, element, map key or value) holds only Dir values and starts at the first one. auto d = Dir::north; is a Dir.
  • Enum to int is implicit: int n = d + 1;.
  • Int to enum needs cast<Dir>(n), which raises for a number the enum lacks. A bare int or another enum's value into a Dir slot is a load error, and so are d += 1 and d++.
  • A var holds the integer: type_of reports "int", and it enters a Dir slot when it is one of the values.
  • print, to_string, format, to_json and to_binary show or write the integer; a load restores the enum.
  • Overloads tell f(Dir) from f(int), catch (Dir e) takes a thrown Dir, and a map literal takes values as keys.
  • A value that does not exist fails the load: no value 'zzz' in enum 'Dir'.
jaiscript
enum Dir { north, south, east, west }
Dir d = Dir::south;
int n = d + 1;                               // enum to int: implicit
string name(Dir where) { return where == Dir::east ? "east" : "elsewhere"; }
print("{} {} {} {}", d, type_of(d), n, name(cast<Dir>(n)));
switch (d) {
    case Dir::north: print("up");
    case Dir::south: print("down");
    default: print("sideways");
}
map<Dir, string> short_names = {Dir::north: "N", Dir::south: "S"};
var v = d;
print("{} {}", short_names[d], type_of(v));
try { d = cast<Dir>(9); } catch (e) { print("caught: {}", e); }
output
1 Dir 2 east
down
S int
caught: Cannot cast 9 to 'Dir': not one of its values
jaiscript
enum Dir { north, south, east, west }
Dir d = 2;
output
jaiscript: static check failed:
<script>:2:5: error: cannot initialize 'd' declared 'Dir' with 'int': cast<Dir>(n) converts an int that is one of its values
    Dir d = 2;
        ^

Values. Values count from 0. A value may take a constant integer initializer built from integer and char literals, the enum's earlier values and the integer operators; a value without one is the one before it plus one, and two values may share a number. A name that is not an earlier value, a shift outside 0 to 63 or an overflow fails the load.

The value table. The enum's name alone, used as a value, is a map<string,int> from each value's name to its number, walked in key order. An int slot refuses it at load (an enum's name is its value table, a map<string,int>; name one of its values (Dir::north)).

jaiscript
enum Flag { none, read = 1 << 0, write = 1 << 1, exec = 1 << 2, all = read | write | exec, next }
print("{} {} {} {}", Flag::write, Flag::all, Flag::next, Flag::all & Flag::exec);
for (auto [name, n] : Flag) { print("{} = {}", name, n); }
output
2 7 8 4
all = 7
exec = 4
next = 8
none = 0
read = 1
write = 2

An enum declared inside a function or block is local to it. A program-scope enum of the same name keeps its values, and the checker warns when a local one hides it.

Namespaces​

namespace name { ... } groups functions, variables, classes and enums behind name::. Blocks reopen: every namespace utils { } in the program, in any file, adds to the one namespace. namespace a::b { } and a block nested in namespace a declare the same a::b.

jaiscript
int limit = 100;                              // a global of the same name stays separate
namespace game {
    int limit = 10;
    int bonus = limit * 2;                    // an initializer sees the members above it
    int scale(int v) { return clamp_to(v * 2); }            // declared below: found when called
    int clamp_to(int v) { return v > limit ? limit : v; }   // game::limit hides the global
    int global_limit() { return ::limit; }                  // ::name reaches the global
    class Unit { int hp = 5; }
    namespace ai {
        int think(int v) { return scale(v) + 1; }           // from the enclosing namespace
    }
}
game::limit += 1;
game::Unit u;
print("{} {} {} {} {}", game::scale(7), game::ai::think(2), game::global_limit(), game::bonus, type_of(u));
try { string::to_lower = 3; } catch (e) { print("caught: {}", e); }
output
11 5 100 20 game::Unit
caught: Cannot assign to 'to_lower': it is read-only

Bare names. Code in a namespace (its functions, their lambdas and coroutines, its classes' methods and field initializers) looks a bare name up in this order: locals, the namespace's own members, each enclosing namespace innermost first, then globals. The lookup happens when the code runs, so a function declared later, in a reopening or by a reload is found. Outside the namespace a member's bare name is use of undeclared identifier.

Rules.

  • A namespace variable is one variable; ns::x = v stores under its declared type. The namespace's top-level code runs in order, as a file's does (top-level order).
  • Functions overload as anywhere (Functions). Redefining an existing signature needs override: Function 'f' already exists in namespace 'n'. Use 'override' keyword to replace it.
  • A class declared in a namespace is named ns::Box from every file; that is what type_of reports and a saved $type carries. Inside the namespace the bare Box works. Two namespaces that each declare a Box declare two classes.
  • A namespace may share a class's name. Its functions replace the class's static methods of matching arity when they say override; without it the collision is an error.
  • A qualified name the namespace does not declare fails the load: no member 'sqroot' in namespace 'math'. Every declaration counts (any file, an earlier run, the host), so a script's namespace math { } adds to math::. A file with a dynamic-path include gets a warning instead.
  • The host's entries (string::to_lower, time::now) are read-only.
  • Any reserved word but this and super names a namespace member or an enum value, reached qualified: spider::catch(url), Mode::default (keyword member names).

using ns::name;​

using geo::twice, geo::Pt; makes each name stand for its qualified form in the rest of that file, as a type, a call or a value. There is no using namespace. It is a rewrite of that file's text: files that include or import this one do not inherit it, a name after ., ?., :: or -> is left alone, and declaring the name again in the file is a parse error.

jaiscript
namespace geo {
    float twice(float v) { return v * 2.0; }
    class Pt { int x = 1; int y = 2; }
}
using geo::twice, geo::Pt;
Pt p;
print("{} {}", twice(3.0), p.x + p.y);
output
6.0 3

Destructuring​

auto [a, b, c] = arr; unpacks an array by position. Extra elements are ignored and missing ones bind null. In a range-for over a map, auto [key, value] binds each entry.

jaiscript
auto [x, y] = [10, 20, 30];
auto [p, q, r] = [1, 2];
print("{} {} {} {} {}", x, y, p, q, r);
map<string, int> stock = {"ore": 4, "gem": 1};
for (auto [name, count] : stock) { print("{}: {}", name, count); }
output
10 20 1 2 null
gem: 1
ore: 4

The conditional operator​

c ? a : b is right-associative, so a cascade reads top-down without parentheses: hp > 75 ? "healthy" : hp > 25 ? "wounded" : "critical". With an int arm and a float arm the result is a float. When both arms are lvalues of one type the whole expression is one (Value Semantics).

Null-safe access: ?.​

x?.member and x?.method(...) are null when x is null, and the call's arguments are not evaluated. A null in front of a ?. skips the rest of the chain, as in JavaScript and C#. Only the value in front of each ?. is tested, so put one on every link that may be null. ?? supplies a default (Operators).

jaiscript
class Node { int v = 3; shared_ptr<Node> next; }
auto head = new Node();
shared_ptr<Node> none;
print("{} {} {} {}", head?.v, head?.next?.v, none?.next.v, none?.v ?? -1);
try { print(head?.next.v); } catch (e) { print("caught: {}", e); }
output
3 null null -1
caught: Cannot access 'v' on null ('next' is null)