Subscripts and slicing

String, blob, and list subscripts can either retrieve a single element, or can perform slicing. Map subscripts can refer only to one element, not a slice, since maps are unordered.

The syntax and semantics for subscripting with an index or a slice match the rules in Python. All string, blob, and list indexing is zero-based, and slices include the lower bound but exclude the upper bound. For example, if a is a list, then a[2:5] is the same as the list [a[2], a[3], a[4]]. If the lower bound is omitted, the slice starts at element zero; if the upper bound is omitted, the slice continues until the last element. For example, if the last element of a has index 9, then a[7:] is the same as the list [a[7], a[8], a[9]]. Here is the syntax:
subscriptExpr ::= expr '[' subscript ']'
subscript     ::= expr  | ( expr? ':' expr? )

String subscripts and slices are character-based, and result in strings. For example, if s is a ustring, then s[3] retrieves the third Unicode character when you count from 0, which is also of type ustring. For rstring values, subscripts and slices are also character-based, but all characters are 8-bit, so index computations are always constant time.

Invalid subscripts cause runtime errors. Runtime errors explains what happens upon errors. A subscript is invalid if it is out-of-bounds for its collection, unless it is the target of an assignment to a new key in a map. For example, if list v has three elements, then only indices 0 <= i <= 2 are valid, and v[i] is a runtime error for all i>=3. However, if map m has no key a, then the assignment m["a"] = 1 performs autovivification (inserts a new key) for a and maps it to the value 1. SPL supports autovivification for consistency with Python and with the C++ standard libraries, but restricts it to cases where the assignment is a pure write, without an earlier read step. For example:
mapOfInt["newKey"]            = 3; //auto-vivify "newKey"
tupleOfMap.m["newKey"]        = 5; //auto-vivify "newKey"
mapOfMap["oldKey"]["newKey"]  = 5; //auto-vivify "newKey"
mapOfMap["newKey"]["oldKey"]  = 5; //error: must read from "newKey" first
mapOfInt["newKey"]           += 2; //error: must read from "newKey" first
mapOfTuple["newKey"].b        = 5; //error: must read from "newKey" first
A slice x[lower:upper] is valid even if lower or upper is out of bounds. Here are some examples:
void test() {
  list<rstring> x = ["a", "b", "c"];
  rstring s = x[4]; //runtime error: index out of bounds
  mutable list<rstring> y;
  y = x[1 : 5];  // ["b", "c"]
  y = x[5 : 5];  // [ ]
  y = x[5 : 1];  // [ ]
  y = x[5 :  ];  // [ ]
  y = x[0 : 2];  // ["a", "b"]
  y = x[  : 2];  // ["a", "b"]
  y = x[2 : 0];  // [ ]
}
Tip: If you are not certain whether a subscript is valid, guard it with a defensive membership test.
For example:
rstring munchkinLand(map<rstring, rstring> places) {
  if ("Oz" in places)
    return places["Oz"];
  return "not found";
}