Reference types

These types are passed by reference.  They need to be constructed with
NEW() before they can be used.  The initial value of a variable for a reference
type is NIL.


Varstring


Behaves like a one dimensional array of bytes.  Can contain NUL bytes.
The normal meaning is UTF-8 text, all operations assume the string is
UTF-8 text.  E.g. slice(2) removes the first two characters, which may be
more than two bytes.

Implemented in an optimal way: When immutable and not containing a NUL
byte a C string is used.  When mutable a list of string parts with
extra space is used to avoid making a copy when appending.  And when
taking a slice it may reference the original string with an offset and length,
using a copy-on-write mechanism.

It would be possible to use different types: MutableString, Cord, StringPiece,
etc.  In practice this means many functions need to accept several of these
types and support them explicitly.  It's a lot simpler to use one type and
let the compiler figure out how to implement it efficiently.

There are three types of initializers:
         string s = "foobar\n"         # backslash has special meaning
         string s = R"\foo""\bar"      # only " has special meaning, double to get one
         string s = '''literal
                  ''''''text'''        # use six ' to get three

A string is mutable:
         string s = "foo"
         s.append("bar")
         s.makeUpper()

It is possible to get to the raw bytes:
        s.asBytes()[4]                 # get the fourth byte

String

Contains the same data as a string, but is immutable.  This is an efficient way of
handling strings that will not change.  For short strings the length is stored as
one byte.  An empty string only takes one byte.

A string can be used anywhere a string is used, it is automatically converted.
When a varstring is assigned to a string a read-only copy is made.

Varbytes

This is the same as string, except that no character operations are allowed.
This is used for raw data, where the meaning of the bytes is unknown.
Indices work on bytes, not characters.

Implementation is exactly the same as for string, conversion
between the two does not change the contents:
         bytes b = "foobar"            # use string as bytes
         b.makeSlice(3, 4)             # take two bytes
         string s = b.asString()       # use bytes as a string

Bytes

This is an immutable bytes, just like what string is to varstring.

Cstring

A NUL terminated string of bytes.

Array

Array of one data type, multi-dimensional.
Resizing is possible but inefficient.
All items must have the same type, ANY can be used but is inefficient.
        int[100] counters
        int[] oneTwoThree = [1, 2, 3]

        string[100] names = ["Monday", "Tuesday"]

Tuple

Array with a fixed size and mixed types.
Useful for a function return value:
        FUNC tuple<int, string, list<string>> getValues()
          ...
        }
        int n
        string name
        list<string> list
        [n, name, list] = getValues()     # tuple/list/array unpack

Initializers:
          [< 1, "hello", ["a", "b"] >]      # implied types
          tuple<int, string>(123, "foo")
          tuple<ANY, ANY>   # item containing two arbitrary items

List

Ordered sequence of one data type, accessed by index (first one is 0).
Inserting items and deleting items is possible and efficient.
The implementation uses an array or linked list, whatever is more
efficient.
      list<string> words  # list of strings
      list<ANY> list      # list of anything

Initializers:
         [1, 2, 3]                        # implied type.
         list<float>.NEW([1, 2, 0.3])     # specified type
         list<ANY>.NEW([1, "word", 0.3])  # any type

Dict

Dictionary: unordered collection of any data type, accessed by a key.
      dict<string, Int>     # key is string, value is Int
      dict<string, ANY>     # key is string, any type of value
      dict<ANY, ANY>        # key is any type, value is any type

Initializers:
         ['a': 1, 'b': 2]                   # use type of first entry, implied type
         dict<string, float>.NEW(['a': 3])  # specified type
         dict<string, ANY>.NEW(['a': 1, 'b': "x"])
         dict<string, int> mydict = [:]     # empty dictionary

MultiDict

Documentation page: here

Like dict, but multiple values per key possible (as a list).
      multiDict<string, string> mdict = NEW()
      mdict.add("Smith", "John")
      mdict.add("Smith", "Peter")
      mdict.count("Smith")   # 2
      list<string> firstNames = mdict.get("Smith")
      mdict.remove("Smith", "Peter")
      mdict.clear("Smith")

Initializer works like a dict:
         ["Smith": "Peter", "Smith": "John"]

TODO: Is there a way to avoid repeating the key?  Perhaps:
         ["Smith": "Peter"; "John"]

Set

Documentation page: here

Like dict but without a value, only whether key is present or not.
See Python set and frozenset
        set<String> mySet = NEW()
        mySet.add("Peter")
        mySet.remove("Peter")  # same as set.clear("Peter")
        bool hasPeter = mySet.has("Peter")
        mySet.get("Peter")     # error
        mySet.count("Peter")   # 0 or 1
        bool hadPeter = mySet.pop("Peter")

Initializer uses a list:
         ["Peter", "John"]

MultiSet

Count number of times a key has been added.
        multiSet<string> mset = NEW()
        mset.add("Peter")
        mset.add("Peter")
        mset.count("Peter")   # 2
        mset.remove("Peter")
        mset.count("Peter")   # 1
        mset.get("Peter")     # compile Error
        mset.clear("Peter")

Initializer uses a list:
         ["Peter", "John", "Peter", "Maca"]