Srl Library Reference


Standard Runtime Library consists of one module named `Srl` which contains the following modules and classes:

Array Class

A dynamic array template that automatically manages the array buffer, taking performance into account and avoiding unnecessary memory allocation and copying operations. The following example shows usage of this dynamic array.
func main {
  def a: Array[Int] = getArray();
  a.add(1); // Here the allocated memory block is expanded.
  def a2: Array[Int] = a; // No new memory block is allocated here.
  a.add(2); // Now a new copy of the buffer is created so a2 is not affected.
  printArray(a); // No new memory block is created or copied here.
  // Now the memory blocks of a and a2 are freed.
}

func getArray(): Array[Int] {
  def a: Array[Int];
  a.add(0);
  return a; // No new memory block is allocated here and memory copy happens.
}

func printArray (a: Array[Int]) {
  def i: Int;
  for i = 0, i < a.getLength(), ++i Console.print("%d\n", a(i));
}
`Array` class contains the following members:
  • Initialization
      handler this~init(ref[Array[ContentType]]);
      handler this~init(count: Int, args: ...ContentType);
    
    The first form initializes an array from another. The new array will use the same content of the given array, and no copy will happen until one of them changes the content. In that case, the content is copied before the change occurs to ensure that the other array is not affected.
    The second form initializes the array from the given items, as shown in the example below:
    def a1: Array[Int]({ 5, 2, 1 }); // Array will contain 3 elements: 5, 2, and 1.
    def a2: Array[Int](3, 5, 2, 1); // Array will contain 3 elements: 5, 2, and 1.
    
  • getLength
    handler this.getLength (): ArchInt;
    
    Returns the number of items in the array.
  • getBufSize
    handler this.getBufSize (): ArchInt;
    
    Returns the number of items that the current allocated memory could store. When exceeding that size the object will extend the memory capacity automatically.
  • assign
    handler this.assign (a: ref[Array[ContentType]]);
    
    Assigns new content to the array from another array. This function does not copy the contents of the other array, instead both arrays share the same buffer until one of the them needs to change its content, in which case that array will copy the buffer and end the sharing.
  • add
    handler this.add (e: ContentType);
    handler this.add (argCount: Int, args: ...ContentType);
    handler this.add (e: Array[ContentType]);
    
    The first form adds an item to the array after extending the allocated memory if necessary. If the content is shared with another array, this function copies the content to a new buffer.
    The second form adds many items at once.
    The third form adds an array to the current array. It adds all items of the given array to the current array. The given array will not be affected by this call.
    a.add({ 5, 2, 1 }); // Adds 3 elements: 5, 2, 1.
    a.add(3, 5, 2, 1); // Adds 3 elements: 5, 2, 1.
    
  • insert
    handler this.insert (index: ArchInt, element: ContentType);
    
    Adds new item to the array in the specified location after extending the allocated memory if necessary. If the content is shared with another array, this function copies the content to a new buffer.
  • remove
    handler this.remove (index: ArchInt);
    
    Removes the item at the specified index. If the content is shared with another array, this function copies the content to a new buffer.
  • slice
    handler this.slice (start: ArchInt, count: ArchInt);
    
    Copys part of the array and returns it as a new array. The copy starts from the item at index `start` and continues until `count` items are copied or the end of the array is reached.
  • clear
    handler this.clear ();
    
    Removes all items from the array. If the content is not shared with another array the buffer will be released.

String Class

`String` class simplifies dealing with strings. It is responsible for allocating and releasing the memory allocated for the string, taking performance into account and avoiding unnecessary memory allocation and copy operations. This class provides functions to simplify different operations on strings. The following example shows this class in use.
func main {
  def str: String = getString();
  str += " world"; // Memory block is expanded here.
  def str2: String = str; // No new memory is allocated here.
  str += "."; // Copy of the string buffer is created so str2 is not affected.
  printStr(str); // No new memory allocation or copy happens here.
  // Now memory blocks of str and str2 are freed.
}

func getString (): String {
  def s: String = "Hello";
  return s; // No new memory allocation or copy happens here.
}

func printStr (s: String) {
  Console.print(s); // s is automatically casted into ptr[array[Char]].
}
`String` class contains the following members:
  • buf
    def buf: ptr[array[Char]];
    
    A pointer to this string content.
  • parentheses operators
    handler this(i: ArchInt): Char
    
    This operator could be used to retrieve the character at the specified location.
  • comparison operators
    handler this == ptr[array[Char]]: Bool
    handler this > ptr[array[Char]]: Bool
    handler this < ptr[array[Char]]: Bool
    handler this >= ptr[array[Char]]: Bool
    handler this <= ptr[array[Char]]: Bool
    
    Compares this string to the given string.
  • getLength
    1: handler this.getLength (): ArchInt;
    2: func getLength (p: ptr[array[Char]]): ArchInt;
    
    1. Returns this string's length.
    2. Returns the length of the given string.
  • alloc
    handler this.alloc (ArchInt);
    
    Allocate memory in advance. This function allows the user to allocate memory in advance for use with string operations that deals with the string buffer directly. This function is useful for dealing with libraries that deal with char pointers, while benefiting from the memory management functionality that this class provides.
  • realloc
    handler this.realloc (ArchInt);
    
    Changes the size of allocated memory for this string. This function enables the user to change the buffer size while doing string operations directly on the buffer.
  • assign
    1: handler this.assign (str: ref[String]);
    2: handler this.assign (buf: ptr[array[Char]]);
    3: handler this.assign (buf: ptr[array[Char]], count: ArchInt);
    4: func assign (target: ptr[array[Char]], fmt: ptr[array[Char]], ...any): Int;
    
    1. Assigns new content to ths string from another string. This function does not copy the content of the other string, instead they both share the same buffer until one of them needs to change it, at which point the buffer is copied and the sharing ends.
    2. Assigns new content for the string from a buffer in the memory. This function copies the content from that buffer into a new buffer managed by this string.
    3. This is similar to the second variant but it copies only the specified number of characters from the given buffer.
    4. Assigns new value from a `format` and unspecified number of arguments to the target. User should ensure that there is enough memory in the target to hold the whole result.
    User could also use `=` operator to replace functions 1 and 2.
  • append
    1. handler this.append (buf: ptr[array[Char]]);
    2. handler this.append (buf: ptr[array[Char]], count: ArchInt);
    3. handler this.append (c: Char);
    4. handler this.append (i: Int[64]);
    5. handler this.append (f: Float[64]);
    
    1. Appends the given buffer to the content's end of the string.
    2. Appends the specified number of of characters from the given buffer.
    3. Appends a character to the string's end.
    4. Appends the given number to the end of the string. This function appends a string representation of the given number.
    5. This is similar to the 4th variant but it appends a float number instead of an integer.
    It is possible to replace these functions (except for the second function) by `+=` operator.
  • concat
    1. handler this.concat (buf: ptr[array[Char]]);
    2. handler this.concat (buf: ptr[array[Char]], count: ArchInt);
    3. handler this.concat (c: Char);
    4. handler this.concat (i: Int[64]);
    5. handler this.concat (f: Float[64]);
    6. func concat (target: ptr[array[Char]], source: ptr[array[Char]]): ptr;
    7. func concat (target: ptr[array[Char]], source: ptr[array[Char]], count: ArchInt): ptr;
    
    1-5. These functions are similar to `append` functions but they return the value in new string instead of editing the current string. It is possible to replace functions 1, 3, 4, and 5 with the `+` operator.
    6. Append the source string to the end of target string. User should ensure that the target buffer is enough to hold the new items.
    7. This is similar to variant 6 but it only copies the specified number from the source string.
  • find
    1. handler this.find (buf: ptr[array[Char]]): ArchInt;
    2. handler this.find (c: Char): ArchInt;
    3. func find (haystack: ptr[array[Char]], needle: ptr[array[Char]]): ptr[array[Char]];
    4. func find (haystack: ptr[array[Char]], c: Char): ptr[array[Char]];
    
    1. Searches for a string inside this string. It returns the start location of the found string, or -1 if nothing is found.
    2. Searches for a character inside this string. It returns the location of the found character, or -1 if nothing is found.
    3. Searches for a string inside the given string. It returns a pointer to the beginning of the found string, or 0 if nothing is found.
    4. Searches for a character inside the given string. It returns a pointer to the found character, or 0 if nothing is found.
  • findLast
    1: handler this.findLast (buf: ptr[array[Char]]): ArchInt;
    2: handler this.findLast (c: Char): ArchInt;
    3: func findLast (haystack: ptr[array[Char]], needle: ptr[array[Char]]): ptr[array[Char]];
    4: func findLast (haystack: ptr[array[Char]], c: Char): ptr[array[Char]];
    
    These functions are similar to `find` functions, but they start the search from the end of the string instead of the beginning.
  • compare
    1. handler this.compare (buf: ptr[array[Char]]): Int;
    2. handler this.compare (buf: ptr[array[Char]], count: ArchInt): Int;
    3. func compare (str1: ptr[array[Char]], str2: ptr[array[Char]]): Int;
    4. func compare (str1: ptr[array[Char]], str2: ptr[array[Char]], count: ArchInt): Int;
    
    1. Compares the current string with the given string and returns 1 if the current one is bigger, -1 if it is smaller, or 0 if they are the same.
    2. This is similar to the first function but it compares only the specified number of the given buffer's items.
    3. This is similar to the first function bit it compares the two given arrays of chars.
    4. This is similar to function 3 but it compares only a specified number of characters from the second array.
  • replace
    1. handler this.replace (match: ptr[array[Char]], replacement: ptr[array[Char]]): String;
    2. func replace (chars: ptr[array[Char]], from: Char, to: Char): ptr[array[Char]];
    
    1. Replaces a part of the string with another string, and returns the result in a new string.
    2. Replaces all occurances of a character with another character in the given array of chars. This function modifies the given array of chars.
  • trim
    handler this.trim (): String;
    
    Removes the spaces (space, new line, or tab characters) from both sides of the string and returns the result in a new string.
  • trimStart
    handler this.trimStart (): String;
    
    This is similar to `trim` function but it trims from the beginning only.
  • trimEnd
    handler this.trimEnd (): String;
    
    This is similar to `trim` function but it trims from the end only.
  • toUpperCase
    handler this.toUpperCase (): String;
    
    Replaces the latin small characters with their corresponding capital characters and returns the result in a new string.
  • toLowerCase
    handler this.toLowerCase (): String;
    
    Replaces the latin capital characters with their corresponding small characters and returns the result in a new string.
  • slices
    handler this.slice (start: ArchInt, count: ArchInt): String;
    
    Returns a part of the current string in a new string.
  • split
    handler this.split (separator: ptr[array[Char]]): Array[String];
    
    Splits the current string into an array of strings using the given separator. The separator is not included in the result. If that separator is not found, it returns and array of one element which is the whole string.
  • merge
    func merge (parts: Array[String], separator: ptr[array[Char]]): String;
    
    Returns a new string which contains the given parts separated with the given separator.
  • copy
    1. func copy (target: ptr[array[Char]], source: ptr[array[Char]]): ptr;
    2. func copy (target: ptr[array[Char]], source: ptr[array[Char]], count: ArchInt): ptr;
    
    1. Copies the array of chars from the source to the target. User should ensure that the memory allocated for target is enough to hold the source content.
    2. This is similar to the first function except that it copies only the specified number of items from the source.
  • scan
    func scan (source: ptr[array[Char]], fmt: ptr[array[Char]], ...any): Int;
    
    Scans the given string searching for the given items in the format and returns those items in the arguments after the format. This function is similar to `sscanf` system function.
  • isSpace
    func isSpace (c: Char): Bool;
    
    Tells whether the given character is a space, a tab, a new line, or a carriage return.
  • isEqual
    func isEqual (str1: ptr[array[Char]], str2: ptr[array[Char]]): Bool;
    
    Tells whether the two given strings are the same.
  • remove
    func remove (chars: ptr[array[Char]], c: Char): ptr[array[Char]];
    
    Removes every match of the given character from the given array of chars. Removing that character results in shifting following characters to replace it. This function modifies the provided string.
  • format
    func format (fmt: ptr[array[Char]], values: ...any): String;
    
    Create a new string from given format after filling it with the given arguments. This function is similar to `sprintf` function, by replacing symbols starting with `%` in the format by a value from arguments that match the type specified. The symbol `%` is followed by a character specifying the type of the given argument. As follows:
    • %s Array of chars.
    • %c A single char.
    • %i 32 bits integer
    • %l 64 bits integer
    • %f 32 bits float
    • %d 64 bits float
    • %% Prints the `%` symbol
  • parseInt
    func parseInt (str: ptr[array[Char]]): Int[64];
    
    Reads an integer from the given array of chars and returns it.
  • parseFloat
    func parseFloat (str: ptr[array[Char]]): Float[64];
    
    Reads a float from the given array of chars and returns it.
  • parseHexDigit
    func parseHexDigit (Char): Int;
    
    Parses the given HEX character.

StringBuilder Class

The `StringBuilder` type is used to build strings while reducing the number of memory allocations resulting from multiple string concatenations. It is highly recommended to use this class instead of the plain `String` class when constructing strings through high numbers of concatentations.
class StringBuilder [formatMixin: ast_ref = _emptyMixin] {
  handler this~init();
  handler this~init(initialSize: ArchInt, growSize: ArchInt);
  handler this~init(str: String, growSize: ArchInt);
  handler this.append (buf: CharsPtr);
  handler this.append (buf: CharsPtr, bufLen: ArchInt);
  handler this.append (c: Char);
  handler this.append (i: Int[64]);
  handler this.append (f: Float[64]);
  handler this.format(fmt: ptr[array[Char]], args: ...any);
  handler this.clear();
  handler this.getLength();
  handler this += CharsPtr this.append(value);
  handler this += Char this.append(value);
  handler this += Int[64] this.append(value);
  handler this += Float[64] this.append(value);
  handler this~cast[ref[String]];
}
The following example shows how to use this class:
func main {
  def sb: StringBuilder(
    1024, // preallocate 1024 bytes
    512   // enlarge memory block by 512 whenever it's full
  );
  sb.append("Result of ");
  sb.format("7 + 7 = %i\n", 7 + 7);
  def s: String = sb;
  Console.print(s); // Prints: Result of 7 + 7 = 14
}
  • Initialization
    handler this~init();
    handler this~init(initialSize: ArchInt, growSize: ArchInt);
    handler this~init(str: String, growSize: ArchInt);
    
    The second form of the constructor preallocates the buffer with the size specified in the first arg, while the second argument specifies the size by which to enlarge the buffer everytime it's full.
    The third form is similar to the second except that it starts by sharing the buffer with the given string until a modification is made, at which point it duplicates the buffer, if necessary, and expands it by the size given in the third argument.
  • apppend
    Appends a value to the string. This is simliar to the `String.append` method.
  • format
    Similar to the `String.format`, with the given differences:
    • It appends the generated string to the buffer instead of creating a new string.
    • It understands the same set of argument types, but the set can also be extended by end users through the use of mixins, as shown later in this section.
  • clear
    Empties the buffer. This only replaces the content of the buffer with an empty string; it doesn't free the buffer.
  • getLength
    Returns the length of the string in the buffer. This is not the size of the buffer itself; it's the size of the string contained in it, which is usually smaller than the buffer.
Extending the Types of `format`
To expand the set of types understood by the `format` method the user can provide the `StringBuilder` class with a mixin containing the set of functions to be called by `format` in response to encountering the related symbol in the format string. In order for the functions within the mixin to be called by `format` they need to receive a single argument of a type matching the requested type, and be flaged with the `@format` modifier, giving that modifier the requested formatting symbol as a param.
The same data type can be used in multiple formatting as long as the formatting symbol is different. For example, the type `Int[64]` can be used in two functions, one having the symbol `%gd` to treat the given integer as a timestamp and print it as a gregorian date, while the other having the symbol `%hd` to also treat the given integer as a timestamp but print it as a hijri date.
The following example shows extending the StringBuilder with a formatting for convertign timestamps to date strings:
def StringBuilderMixin {
  @format["gd"]
  handler this.formatTimestamp(ts: Int[64]) {
    this.append(Time.toString(ts));
  }
}
func main {
  def sb: StringBuilder[StringBuilderMixin](1024, 512);
  def timestamp: Int[64] = ...;
  sb.format("Today's date is %gd", timestamp);
  ...
}

Map Class

A map template that allows specifying the types of the key and the value, and takes the responsibility for managing memory while taking performance into consideration and avoiding unnecessary memory allocation and copying. The following example shows the class in use.
func main {
  def m1: Map[Int, Int] = getMap();
  m1.set(7, 50); // Memory buffer is expanded here.
  def m2: Map[Int, Int] = m1; // Buffer is not cloned here.
  m2.set(12, 7); // Buffer is cloned here so that m1 is not affected.
  printMap(m2); // No buffer cloning happens here.
  printInt(m2(7)); // Prints 50.
  printInt(m2(12)); // Prints 7.
  // Now buffers of m1 and m2 are freed.
}

func getMap (): Map[Int, Int] {
  def m: Map[Int, Int];
  m.add(0, 10);
  return m; // No buffer cloning happens here.
}

func printMap (m: Map[Int, Int]) {
  def i: Int;
  for i = 0, i < m.getLength(), ++i {
    Console.print("%d = %d\n", m.keyAt(i), m.valAt(i));
  }
}
Class `Map` contains the following members:
  • Initialization
      handler this~init (useIndex: Bool);
      handler this~init (ref[Map[KeyType, ValueType]]);
      handler this~init (ref[Map[KeyType, ValueType]], useIndex: Bool);
    
    The first form initializes empty Map and choosing between using an index or not. The use of index increases the speed of searching for items in the Map at the expense of consuming more memory and slowing down write operations.
    The second form initialize the Map from another Map. The new Map will use the same content as the given Map, and no content copy will occurs until one of the two Maps changes its content at which point the content is copied to ensure the other Map is not affected. In this form index won't be used even if the given Map uses one.
    The third form is similar to the second one, but allows the user to use an index. If the user asked for using an index then it will be created even if the given Map does not have one.
  • keys
      def keys: Array[KeyType];
    
    The array of the keys in the map.
  • values
      def values: Array[ValueType];
    
    The array of the values in the map.
  • getLength
      handler this.getLength (): ArchInt;
    
    Returns the number of items in the map.
  • keyAt
      handler this.keyAt (ArchInt): ref[KeyType];
    
    Returns a reference to the key at the given location.
  • valAt
      handler this.valAt (ArchInt): ref[ValueType];
    
    Returns a reference to the value at the given location.
  • set
      handler this.set (
        key: KeyType, value: ValueType
      ): ref[Map[KeyType, ValueType]];
    
    Sets a value for the given key. If that key doesn't exist it will be added. If it already exists its value will be replaced by the new one. This functions will return a reference to the Map itself which allows the user to chain multiple `set` operations in one statement.
  • setAt
      handler this.setAt (
        index: ArchInt, value: ValueType
      ): ref[Map[KeyType, ValueType]];
    
    Sets a new value on the given index, which means changing the value based on its index instead of its key. Returns a reference to the Map itself which allows the user to chain multiple `setAt` operations in a one statement.
  • insert
      handler this.insert (index: ArchInt, key: KeyType, value: ValueType);
    
    Inserts a new key and its value at the given index.
  • remove
      handler this.remove (key: KeyType): Bool;
    
    Removes the specified key and its value. Returns 1 if the key is removed, and 0 if the key does not exist.
  • removeAt
      handler this.removeAt (index: ArchInt);
    
    Removes the specified key and its value at the given index.
  • clear
      handler this.clear ();
    
    Clear all content and starts with an empty Map.
  • findPos
      handler this.findPos (key: KeyType): ArchInt;
    
    Returns the index of the given key, or -1 if the key does not exist.

SrdRef Class

Shared reference template that manages releasing the object automatically when the need for it ends. This reference keeps a count of the number of shared references that point to the same object. Every time one of the reference is terminated, the count decremented by 1. When the count reaches zero, the object is terminated and its memory is released.
func main {
  def x: SrdRef[MyType] = SrdRef[MyType].construct();
  def y: SrdRef[MyType] = x; // Both refs point to same object. Counter is now 2.
  x.release(); // Counter is now 1 and the object is not freed yet.
  y.release(); // Now the object is destructed and its memory is released.
}
`SrdRef` Class contains the following members:
  • obj
    The object pointed to by this reference. In some cases, one could need to access the object itself, like when a regular pointer of reference to an object is needed, or in case of accessing one of object's methods with the same name as one of `SrdRef` methods.
  • refCounter
    The record that holds the number of references that point to the same object and the necessary information to terminate and release the object.
  • alloc
      handler this.alloc (): ref[ObjType];
      func alloc (): SrdRef[ObjType];
    
    This function allocates memory for the object, but does not initialize the object and leaves that to the user who can use `~init`.
  • construct
      handler this.construct ();
      func construct (): SrdRef[ObjType];
    
    This function allocates memory for the object and initialize it also. Initializing the object is done without any arguments, so to use this function the object must enable initialization without arguments. If you need to initialize an object with arguments then you should use `allocate` then `~init`.
  • own
      handler this.own (obj: ref[ObType]);
      func own (obj: ref[ObjType]): SrdRef[ObjType];
    
    This function assigns the object of this reference with a given object. After calling this function the reference takes the responsibility of releasing the object automatically. This function should be avoided if the given object is not allocated dynamically or if another code is responsible for releasing the object, since this will lead to segmentation fault.
  • release
      handler this.release();
    
    Release this reference, and if this reference is the last to point to the object then it will release the object before that.
  • assign
      handler this.assign (r: ref[SrdRef[ObjType]]);
      handler this.assign (r: ref[WkRef[ObjType]]);
      handler this.assign (c: ref[RefCounter], o: ref[ObjType]);
    
    Set a new value to the reference, it changes the reference to point to the given object. The first two functions takes the value of the reference from types `SrdRef` and `WkRef` respectively with the same object type. On the other hand, the third function is used to make the reference points to an object with the same type of this reference, but belongs to a reference with another type. This function is used in casting while ensuring that releasing the object when terminating the reference will leads to releasing the original object with its original type that is created with. The next example explains the difference:
      def x: SrdRef[MyType];
      x.construct();
      def y: SrdRef[SubType];
      y.assign(x.refCounter, x.subObj);
      
      x.release(); // Nothing will be released as y still holds the object.
      y.release(); // Here x itself will be released, not x.subObj.
    
  • isNull
      handler this.isNull(): Bool;
    
    Returns true if the reference is null.

WkRef

Reference template that points to a shared object but it does not participate in owning the object, which means it does not update the reference count of the object. If all `SrdRef` instances pointing to a specific object are terminated then that object will be released even if some `WkRef` instances are still pointing to it. This type of references is useful to avoid circular references which lead to memory leaks. So if you have an object that owns another object through `SrdRef` reference, and the inner object needs a reference to the outer object, then using `SrdRef` in the inner object will lead to a circular reference, whereas using `WkRef` in the inner object won't lead to that because releasing the outer object in this case will cause both objects to be released.
The difference between using `WkRef` and using `ref`, is that the first holds references count's information even if it does not use it, whereas the later does not hold this information, so it is possible to get `SrdRef` from `WkRef` but we could not get `SrdRef` from `ref` because the later does not holds the count information that `SrdRef` needs.
The way to use `WkRef` is the same as `SrdRef`. `WkRef` class has the following items:
  • obj
    The object that this reference points to. In some cases you will need to access the object itself like when you need a pointer or a regular reference to the object, or in case you want to access one of object's methods that has the same name as one of `WkRef` methods.
  • refCounter
    The record that holds the number of references that point to the same object and the necessary information to terminate and release the object.
  • release
      handler this.release();
    
    Release this reference without changing references count.
  • assign
      handler this.assign (r: ref[SrdRef[ObjType]]);
      handler this.assign (r: ref[WkRef[ObjType]]);
      handler this.assign (c: ref[RefCounter], o: ref[ObjType]);
    
    Sets a new value to the reference, it changes the reference to point to the given object. The first two functions takes the value of the reference from types `SrdRef` and `WkRef` respectively with the same object type. On the other hand, the third function is used to make the reference points to an object with the same type of this reference, but belongs to a reference with another type. This function is used in casting while ensuring that releasing the object when terminating the reference will leads to releasing the original object with its original type that is created with. The next example explains the difference:
      def x: SrdRef[MyType];
      x.construct();
      def y: WkRef[SubType];
      y.assign(x.refCounter, x.subObj);
      def z: SrdRef[SubType];
      z = y;
      
      x.release(); // Nothing is released here.
      z.release(); // x will be released here, not x.subObj.
    
  • isNull
      handler this.isNull(): Bool;
    
    Returns true if the reference is null.

UnqRef Class

The simplest type of smart references. This reference is not shared and does not use a reference count. When terminating this reference it will release the object that it owns directly. For example:
  def x: UnqRef[MyType];
  x.construct();
  
  def y: UnqRef[MyType];
  y = x; // Error.
  
  x.release(); // Object will be released here.
This class contains the following items:
  • obj
    The object that this reference points to. In some cases you will need to access the object itself like when you need a pointer or a regular reference to the object, or in case you want to access one of object's methods that has the same name as one of `UnqRef` methods.
  • construct
    handler this.construct();
    
    This function allocate memory for the object and initialize it also. Initialization of the object is done without arguments, so to use this function the object must be able to initialize without arguments.
  • release
    handler this.release();
    
    Release this reference and release the object.
  • isNull
      handler this.isNull(): Bool;
    
    Returns true if the reference is null.

Error Class

A root for classes that hold runtime error information. It contains two abstract methods:
  • getCode
    Returns a code that distinguish between this error and the others.
      handler this.getCode(): String as_ptr;
    
  • getMessage
    Returns a string representation of the error the could be displayed to the user.
      handler this.getMessage(): String as_ptr;
    

GenericError Class

Inherited from `Error` class. It allows the user to specify a custom error code and message manually for each instance. It contains two variables:
  • code: String
  • message: String

Possible Class

Template class to hold information of unspecified type (user specify it) with the possibility of holding an error value in cases of failure. Users who receive a variable of this class should check the error status before using the information held by this class. It contains the following items:
  • value
    The information held by this object. `DataType` class refer to template argument.
      @injection def value: DataType;
    
  • error
    A reference to the error if exists. If this error is set then operation was failed and the information in `value` is not valid.
      def error: SrdRef[Error];
    
  • casting operations
    This class contains the following casting operations:
      handler this~cast[ref[DataType]] return this.value;
      handler this~cast[Bool] return this.error.isNull();
    
    Casting operation as `Bool` returns 1 if the operation succeed, which means the information is valid.
  • success
    A function to ease the creation of an object holding valid value.
      func success (v: DataType): Possible[DataType];
    
  • failure
    A function to ease the creation of error object.
      func failure (err: SrdRef[Error]): Possible[DataType];
    

Nullable Class

Template class to hold information of unspecified type (user specifies it) in addition to `null` value as one of the possible values.
  • value
    The information held by this object. `DataType` class refer to template argument.
      @injection def value: DataType;
    
  • isNull
    Boolean value to specify whether the value is `null` or not.
      def isNull: Bool;
    
  • casting operations
    This class contains casting operation that automatically casts to original information type.
      handler this~cast[ref[DataType]] return this.value;
    

Memory Module

Memory module contains the following functions:
  • alloc
    @expname[malloc] func alloc (size: ArchInt): ptr;
    
    Allocates a memory block on the heap and returns its pointer. This is the same as `malloc` function from POSIX.
  • realloc
    @expname[realloc] func realloc (p: ptr, newSize: ArchInt): ptr;
    
    Changes the size of an allocated memory block. This may result in copying the memory block to a new location. This is the same as `realloc` function from POSIX.
  • allocAligned
    @expname[aligned_alloc]
    func allocAligned (alignment: ArchInt, size: ArchInt): ptr;
    
    This is the same as `aligned_alloc` function from POSIX.
  • free
    @expname[free] func free (p: ptr);
    
    Frees an allocated memory blockl. This is the same as `free` function from POSIX.
  • copy
    @expname[memcpy] func copy (target: ptr, src: ptr, size: ArchInt): ptr;
    
    Copies a memory block to a new location. This is the same as `memcpy` function from POSIX.
  • compare
    @expname[memcmp] func compare (s1: ptr, s2: ptr, size: ArchInt): Int;
    
    Compares two memory blocks. Returns a value < 0 if the first non-matching byte in s1 is smaller than its counterpart in s2. Returns a value > 0 if it's greater. Returns 0 if the two blocks are identical. This is the same as `memcmp` function from POSIX.
  • set
    @expname[memset] func set (s: ptr, c: Int, n: ArchInt): ptr;
    
    Sets all bytes in the specified memory buffer to the given value. This is the same as `memset` function from POSIX.
Custom Memory Management
The definitions `alloc`, `realloc`, `allocAligned`, and `free` are in fact pointers to system functions. The user can override these pointers to provide custom allocators using the function `overrideAllocator`, and can then reset to the default allocator using the function `resetAllocator`.
function overrideAllocator(
    customAlloc: ptr[function (size: ArchInt) => ptr[Void]],
    customRealloc: ptr[function (p: ptr[Void], newSize: ArchInt) => ptr[Void]],
    customAllocAligned: ptr[function (alignment: ArchInt, size: ArchInt) => ptr[Void]],
    customFree: ptr[function (pointer: ptr[Void])]
);

function resetAllocator();
Custom allocators will need to directly reach the system allocation functions, which are listed here:
`sysAlloc`: Corresponds to `alloc`.
`sysRealloc`: Corresponds to `realloc`.
`sysAllocAligned`: Corresponds to `allocAligned`.
`sysFree`: Corresponds to `free`.

Math Module

`Math` module contains the following math functions:
  • abs
    @expname[abs] func abs (i: Int[32]): Int[32];
    @expname[llabs] func abs (i: Int[64]): Int[64];
    @expname[fabsf] func abs (f: Float[32]): Float[32];
    @expname[fabs] func abs (f: Float[64]): Float[64];
    
    Get the absolute value of a number.
  • mod
    @expname[fmodf] func mod (x: Float[32], y: Float[32]): Float[32];
    @expname[fmod] func mod (x: Float[64], y: Float[64]): Float[64];
    
    Get division remainder of two numbers.
  • exp
    @expname[expf] func exp (x: Float[32]): Float[32];
    @expname[exp] func exp (x: Float[64]): Float[64];
    
    This is the same as `expf` and `exp` functions from POSIX.
  • exp2
    @expname[exp2f] func exp2 (x: Float[32]): Float[32];
    @expname[exp2] func exp2 (x: Float[64]): Float[64];
    
    This is the same as `exp2f` and `exp2` functions from POSIX.
  • log
    @expname[logf] func log (x: Float[32]): Float[32];
    @expname[log] func log (x: Float[64]): Float[64];
    
    This is the same as `logf` and `log` functions from POSIX.
  • log2
    @expname[log2f] func log2 (x: Float[32]): Float[32];
    @expname[log2] func log2 (x: Float[64]): Float[64];
    
    This is the same as `log2f` and `log2` functions from POSIX.
  • log10
    @expname[log10f] func log10 (x: Float[32]): Float[32];
    @expname[log10] func log10 (x: Float[64]): Float[64];
    
    This is the same as `log10f` and `log10` functions from POSIX.
  • sqrt
    @expname[sqrtf] func sqrt (x: Float[32]): Float[32];
    @expname[sqrt] func sqrt (x: Float[64]): Float[64];
    
    This is the same as `sqrtf` and `sqrt` functions from POSIX.
  • cbrt
    @expname[cbrtf] func cbrt (x: Float[32]): Float[32];
    @expname[cbrt] func cbrt (x: Float[64]): Float[64];
    
    This is the same as `cbrtf` and `cbrt` functions from POSIX.
  • pow
    @expname[powf] func pow (b: Float[32], e: Float[32]): Float[32];
    @expname[pow] func pow (b: Float[64], e: Float[64]): Float[64];
    
    This is the same as `powf` and `pow` functions from POSIX.
  • sin
    @expname[sinf] func sin (x: Float[32]): Float[32];
    @expname[sin] func sin (x: Float[64]): Float[64];
    
    This is the same as `sinf` and `sin` functions from POSIX.
  • asin
    @expname[asinf] func asin (x: Float[32]): Float[32];
    @expname[asin] func asin (x: Float[64]): Float[64];
    
    This is the same as `asinf` and `asin` functions from POSIX.
  • sinh
    @expname[sinhf] func sinh (x: Float[32]): Float[32];
    @expname[sinh] func sinh (x: Float[64]): Float[64];
    
    This is the same as `sinhf` and `sinh` functions from POSIX.
  • asinh
    @expname[asinhf] func asinh (x: Float[32]): Float[32];
    @expname[asinh] func asinh (x: Float[64]): Float[64];
    
    This is the same as `asinhf` and `asinh` functions from POSIX.
  • cos
    @expname[cosf] func cos (x: Float[32]): Float[32];
    @expname[cos] func cos (x: Float[64]): Float[64];
    
    This is the same as `cosf` and `cos` functions from POSIX.
  • acos
    @expname[acosf] func acos (x: Float[32]): Float[32];
    @expname[acos] func acos (x: Float[64]): Float[64];
    
    This is the same as `acosf` and `acos` functions from POSIX.
  • cosh
    @expname[coshf] func cosh (x: Float[32]): Float[32];
    @expname[cosh] func cosh (x: Float[64]): Float[64];
    
    This is the same as `coshf` and `cosh` functions from POSIX.
  • acosh
    @expname[acoshf] func acosh (x: Float[32]): Float[32];
    @expname[acosh] func acosh (x: Float[64]): Float[64];
    
    This is the same as `acoshf` and `acosh` functions from POSIX.
  • tan
    @expname[tanf] func tan (x: Float[32]): Float[32];
    @expname[tan] func tan (x: Float[64]): Float[64];
    
    This is the same as `tanf` and `tan` functions from POSIX.
  • atan
    @expname[atanf] func atan (x: Float[32]): Float[32];
    @expname[atan] func atan (x: Float[64]): Float[64];
    
    This is the same as `atanf` and `atan` functions from POSIX.
  • atan2
    @expname[atan2f] func atan2 (y: Float[32], x: Float[32]): Float[32];
    @expname[atan2] func atan2 (y: Float[64], x: Float[64]): Float[64];
    
    This is the same as `atan2f` and `atan2` functions from POSIX.
  • tanh
    @expname[tanhf] func tanh (x: Float[32]): Float[32];
    @expname[tanh] func tanh (x: Float[64]): Float[64];
    
    This is the same as `tanhf` and `tanh` functions from POSIX.
  • atanh
    @expname[atanhf] func atanh (x: Float[32]): Float[32];
    @expname[atanh] func atanh (x: Float[64]): Float[64];
    
    This is the same as `atanhf` and `atanh` functions from POSIX.
  • ceil
    @expname[ceilf] func ceil (x: Float[32]): Float[32];
    @expname[ceil] func ceil (x: Float[64]): Float[64];
    
    This is the same as `ceilf` and `ceil` functions from POSIX.
  • floor
    @expname[floorf] func floor (x: Float[32]): Float[32];
    @expname[floor] func floor (x: Float[64]): Float[64];
    
    This is the same as `floorf` and `floor` functions from POSIX.
  • round
    @expname[roundf] func round (x: Float[32]): Float[32];
    @expname[round] func round (x: Float[64]): Float[64];
    
    This is the same as `roundf` and `round` functions from POSIX.
  • random
    @expname[rand] func random (): Int;
    
    This is the same as `rand` function from POSIX.
  • seedRandom
    @expname[srand] func seedRandom (s: Word[32]);
    
    This is the same as `srand` function from POSIX.

Net Module

`Net` module contains functions for making network requests. It has the following members:
  • getBuildDependencies
    func getBuildDependencies(): Array[String];
    
    Returns the required dependencies names needed in case of building executable version of projects that use this module. In the case of `Net` module the function returns one library which is Curl. `Net` module contains the full path for the file, not only its name because Alusus depends on a version of Curl bundled with Alusus.
  • get
    1: func get (
         url: ptr[array[Char]], result: ptr[ptr], resultCount: ptr[Int]
       ): Bool;
    2: func get (
         url: ptr[array[Char]], filename: ptr[array[Char]]
       ): Bool;
    
    1. Get the resource specified by the url and return it.
    2. Get the resource specified by the url and store it in the specified file.
  • uriEncode
    func uriEncode(CharsPtr): String;
    
    URL encodes the given string.
  • uriDecode
    func uriDecode(CharsPtr): String;
    
    URL decodes the given string.

Console Module

`Console` module contains functions for dealing with the terminal. It has the following functions:
  • getChar
    @expname[getchar] func getChar (): Int;
    
    This is similar to `getChar` function from POSIX.
  • putChar
    @expname[putchar] func putChar (c: Int): Int;
    
    This is similar to `putchar` function from POSIX.
  • print
    1. @expname[printf] func print (fmt: ptr[array[Char]], ...any): Int;
    2. func print (i: Int[64]);
    3. func print (f: Float[64]);
    4. func print (f: Float[64], n: Int);
    
    1. This is the same as `printf` function from POSIX.
    2. Prints an integer.
    3. Prints a floating point number.
    4. Prints a floating point number with the specified number of digits to the right of the decimal point.
  • scan
    @expname[scanf] func scan (fmt: ptr[array[Char]], ...any): Int;
    
    This is the same as `scanf` function from POSIX.
  • getInt
    func getInt (): Int;
    
    Asks the user to enter an integer.
  • getFloat
    print getFloat (): Float;
    
    Asks the user to enter a floating point number.
  • getString
    func getString (str: ptr[array[Char]], size: Word);
    
    Asks the user to enter a string.
Style
`Style` module contains functions to control the style of writing in the console. Each one of these functions returns a pointer to an array of chars which could be printed to the console to get the required style.
  • reset
  • bright
  • dim
  • italic
  • underscore
  • blink
  • reverse
  • hidden
  • fgBlack
  • fgRed
  • fgGreen
  • fgYellow
  • fgBlue
  • fgMagenta
  • fgCyan
  • fgWhite
  • bgBlack
  • bgRed
  • bgGreen
  • bgYellow
  • bgBlue
  • bgMagenta
  • bgCyan
  • bgWhite

System Module

`System` module contains the following definitions:
  • sleep
    @expname[usleep] func sleep (w: Word);
    
    This is the same as `usleep` function from POSIX.
  • setEnv
    @expname[setenv]
    func setEnv (
        name: ptr[array[Char]], value: ptr[array[Char]], overwrite: Int
    ): Int;
    
    This is the same as `setenv` function from POSIX.
  • getEnv
    @expname[getenv]
    func getEnv (name: ptr[array[Char]]): ptr[array[Char]];
    
    This is the same as `getenv` function from POSIX.
  • exec
    @expname[system] func exec (cmd: ptr[array[Char]]): Int;
    
    This is the same as `system` function from POSIX.
  • exit
    @expname[exit] func exit (status: Int);
    
    This is the same as `exit` function from POSIX.

Fs Module

`Fs` module contains functionality for dealing with the file system.
  • DirEnt
    class DirEnt {
      def dType: Int[8];
      def dName: array[Char, FILENAME_LENGTH];
    };
    
    Information record about an item from folder items.

  • FileNames؛
    class FileNames {
      def count: Int;
      def names: array[array[Char, FILENAME_LENGTH]];
    }
    
    A list of files' names.

  • Seek
    def Seek: {
      def SET: 0;
      def CUR: 1;
      def END: 2;
    };
    
    Constants to deal with `seek` function.

  • exists
    func exists (filename: ptr[array[Char]]): Bool;
    
    Check if a specific file or folder exists.
  • rename
    @expname[rename]
    func rename (oldName: ptr[array[Char]], newName: ptr[array[Char]]): Int;
    
    Renames a file or a folder.
  • remove
    @expname[remove] func remove (filename: ptr[array[Char]]): Bool;
    
    Removes a file or a folder.
  • openFile
    @expname[fopen]
    func openFile (filename: ptr[array[Char]], mode: ptr[array[Char]]): ptr[File];
    
    Opens a file. This is the same as `fopen` function from POSIX.
  • closeFile
    @expname[fclose] func closeFile(f: ptr[File]): Int;
    
    Closes an open file. This is the same as `fclose` function from POSIX.
  • print
    @expname[fprintf]
    func print (f: ptr[File], fmt: ptr[array[Char]], ...any): Int;
    
    Print a string to the file. This is the same as `fprintf` function from POSIX.
  • scan
    @expname[scanf]
    func scan (f: ptr[File], fmt: ptr[array[Char]], ...any): Int;
    
    Reads inputs from the file. This is the same as `scanf` function from POSIX.
  • write
    @expname[fwrite]
    func write (
        content: ptr, size: ArchInt, count: ArchInt, f: ptr[File]
    ): ArchInt;
    
    Writes a memory block to a file. This is the same as `fwrite` function from POSIX.
  • read
    @expname[fread]
    func read (
        content: ptr, size: ArchInt, count: ArchInt, f: ptr[File]
    ): ArchInt;
    
    Reads a block of data from the file. This is the same as `fread` function from POSIX.
  • flush
    @expname[fflush] func flush (f: ptr[File]): Int;
    
    Flushes the write buffer. This is the same as `fflush` function from POSIX.
  • tell
    @expname[ftell] func tell (f: ptr[File]): ArchInt;
    
    Get the current seek position of the file. This is the same as `ftell` function from POSIX.
  • seek
    @expname[fseek]
    func seek (f: ptr[File], offset: ArchInt, seek: Int): Int;
    
    Moves the file read/write pointer. This is the same as `fseek` function from POSIX.
  • createFile
    func createFile (
        filename: ptr[array[Char]], content: ptr, contentSize: ArchInt
    ): Bool;
    
    Creates a file and store the given content in it.
  • readFile
    func readFile (
        filename: ptr[array[Char]], result: ptr[ptr], size: ptr[ArchInt]
    ): Bool;
    
    Reads the full contents of the file and returns it in a new block of memory. The caller is responsible for releasing the allocated memory buffer.
  • makeDir
    func makeDir (folderName: ptr[array[Char]], mode: Int): Bool;
    
    Create a new folder.
  • openDir
    @expname[opendir] func (folderName: ptr[array[Char]]): ptr[Dir];
    
    Opens a folder for reading. This is the same as `opendir` function from POSIX.
  • closeDir
    @expname[closedir] func (folder: ptr[Dir]): Int;
    
    Closes an open folder. This is the same as `closedir` function from POSIX.
  • rewindDir
    @expname[rewinddir] func rewindDir (dir: ptr[Dir]);
    
    Reset the position of a directory stream to the beginning of a directory. This is the same as `rewinddir` function from POSIX.
  • readDir
    1. @expname[readdir] func readDir (dir: ptr[Dir]): ptr[DirEnt];
    2. func readDir (name: ptr[array[Char]]): ptr[FileNames];
    
    1. This is the same as `readdir` function from POSIX.
    2. Return a list of files' names in a speicic folder.

Regex Module

`Regex` module contains functions for dealing with regular expressions.
  • match
    func match (
        pattern: ptr[array[Char]], str: ptr[array[Char]], flags: Int
    ): Array[String];
    
    Apply the given pattern to the given string and return the result as an array if strings. In case of match the array contains the whole match for the pattern in the first item in the array, whereas the following items contain the partial match determined by the pattern inside parentheses. In case of no match, the result is an empty array.
  • Matcher
    class Matcher {
        handler this~init(pattern: ptr[array[Char]]);
        handler this~init(pattern: ptr[array[Char]], flags: Int);
        handler this.initialize(pattern: ptr[array[Char]]);
        handler this.initialize(pattern: ptr[array[Char]], flags: Int);
        handler this.release();
        handler this.match (str: ptr[array[Char]]): Array[String];
    }
    
    A class that allows the user to intialize regular expression then use it in multiple searching operations. The method `match` applies the pattern to the given string and returns an array of strings. In case there exists a match the array contains the whole match of the pattern in the first item in the array whereas the following items contain the partial match determined by the pattern inside parentheses. In case of no match, the result is an empty array.

Time Module

`Time` module contains the following definitions:
  • Time
    class DetailedTime {
      def second: Int;
      def minute: Int;
      def hour: Int;
      def day: Int;
      def month: Int;
      def year: Int;
      def weekDay: Int;
      def yearDay: Int;
      def daylightSaving: Int;
      def timezoneOffset: Int[64];
      def timezone: ptr[array[Char]];
    };
    
    A record that holds the date and time information.
  • getDetailedTime
    1. @expname[localtime] func getDetailedTime (
         ts: ptr[Word[64]]
       ): ptr[DetailedTime];
    2. @expname[localtime_r] func getDetailedTime (
         ts: ptr[Word[64]], ptr[DetailedTime]
       ): ptr[DetailedTime];
    
    1. This is the same as `localtime` function for POSIX.
    2. This is the same as `localtime_r` function for POSIX.
  • getTimestamp
    @expname[time] func getTimestamp (result: ptr[Word[64]]): ptr[Word[64]];
    
    Returns the time as the number of seconds since the Epoch. This is the same as `time` function for POSIX.
  • getClock
    @expname[clock] func getClock (): Int[64];
    
    This is the same as `clock` function for POSIX.
  • toString
    @expname[clock] func toString (ts: ptr[Word[64]]): ptr[array[Char]];
    
    This is the same as `clock` function for POSIX.

Other definistions

  • castRef
    A macro that is used to represent a reference as a reference of another type. This macro accepts two arguments, the first is the variable we want to represent, and the second is the type we want to represent to its reference.

  • nullRef
    A macro that returns a reference with a pointer value of 0. It takes one argument which is the type of the reference's target.

  • getThisSourceFullPath
    A macro that gives a string that contains the full path to the current source code file. It does not take any arguments.

  • getThisSourceDirectory
    A macro that returns a string containing the full path to the directory containing the current source code file. This macro does not take any arguments.

Closure Library Reference


This library adds support for closures. Closures are functions that carry needed environment data with them, i.e. carries a payload which includes values of outer variables accessed by the function.
Inner functions can access global variables outside of it, but it can not access local variables inside an outer function that contains the inner function, because the outer function could be terminated and its variables removed from memory before the inner function is called. For example:

def pf: ptr[func];
func prepareFunc {
    def i: Int = 10;
    pf = func {
        Console.print("%d\n", i); // Error: by the time this line is executed
            // i would have been removed from memory
    };
}

prepareFunc();
pf();
To enable an inner function to access the outer function's variable, the function must carry a copy from the data that it uses from the external function, which is what closures provide. Closures are provided by `closure` library. The compiler automatically prepares a payload that encapsulates accessed outer variables and attaches them to the clsoure. The definitions of the closure are similar to inner function definition except that they use `closure` keyword instead of `function` keyword. Example of closure:
import "closure";

def pc: closure ();
func prepareClosure {
    def i: Int = 10;
    pf = closure () {
        Console.print("%d\n", i); // Correct: access to i will be replaced
            // by the compiler to an access to a copy of i
    };
}

prepareClosure();
pc();
We can notice from the previous example that writing a closure without a body makes it a closure definition, and it is important to know that closure record contains a pointer to the function in addition to a shared reference to information record. Responsibility remains on the programmer to ensure that accessing shared references will not lead to circular references, and hence a memory leak. For example, if you access a shared reference from a closure and that reference is pointing to the record that contains the closure, then this will result in memory leak (the record holds the closure which in turn holds the record so both will stay in the memory). In that case, all we need to do is to make the closure hold a non-shared reference to avoid memory leaks. For example:
import "closure";

class Record {
    def c: closure ();
    def i: Int;
}

def r1: SrdRef[Record];
r1.construct();
r1.i = 10;
r1.c = closure() {
    Console.print("%d\n", r1.i);
};
r1.release(); // r1 will not be released and neither will the closure it owns.

def r2: SrdRef[Record];
r2.construct();
r2.i = 10;
def r22: WkRef[Record] = r2;
r2.c = closure() {
    Console.print("%d\n", r22.i);
};
r2.release(); // r2 will be released along with the closure it owns.
Closure can receive values and return values as regular functions.
import "closure";

def pc: closure (Float): Int;
func prepareClosure {
    def i: Int = 10;
    pf = closure (j: Float): Int {
        return i * j;
    };
}

prepareClosure();
Console.print("%d\n", pc(3.5)); // Prints 35

Data Capture Modes

It is possible to specify the way data is captured from the closure's surrounding context, and it is possible to specify that to each variable independently. For example, if you want to read a variable from the surrounding context but you don't want to modify its value inside the closure, then it is enough to capture a copy of that variable, whereas if you want to change the value of a variable inside the closure then you need to capture a reference to the variable instead. To control the way to capture data, we should follow the following form in the closure's definition:
closure (/*capture_mode_defs*/)&(/*args_defs*/): /*ret_type*/ {
  // closure body
}
In the next example we set the way to capture `n` as `by_ref` and that tells the closure to store a reference to that variable instead of a copy, so changing the variable's value inside the closure will lead to changing the original variable outside the closure.
func testCaptureByRef (n: Int) {
    def c: closure (i: Int): Int;
    c = closure (n: by_ref)&(i: Int): Int {
        return i * n++;
    };
    print("n before calling c: %d\n", n);
    print("closure result: %d\n", c(3));
    print("n after calling c: %d\n", n);
}
testCaptureByRef(5);
/*
output:
n before calling c: 5
closure result: 15
n after calling c: 6
*/

In that example if we change the way if capturing `n` from `by_ref` to `by_val` then the output after calling the closure is 5 instead of 6.

The following modes of data capturing are available:
  • by_ref: closure captures a reference to the variable. if the variable is a reference then the closure will captures that reference, not a reference to the reference.
  • by_val: closure captures the variable's value. If that variable is a reference, then the closure will capture the value that this reference points to, not the reference itself.
  • identical: closure captures the variable literally. If that variable is a reference then the closure will capture the reference, and if it is a value then it will capture the value.
  • auto: If you don't set the capturing way manually then auto way will be used. In that way the compiler determines the way to capture based on the variable's type. If it is a user-defined type and not `SrdRef` then it will use `identical`, otherwise it will use `by_val` instead.
It is possible to check whether the closure's pointer is null or not by using `isNull` function.
  def c: closure (i: Int): Int;
  c.isNull() // returns true
  c = closure (i: Int): Int { return 0 };
  c.isNull() // returns false

Mixins


Mixins are available through the `insertMixin` macro of the `AstMgr` class, which can be called from the body of a class to insert a mixin into it, as shown in this example:
def MyMixin: {
  handler this.print() {
    Console.print(this.toString());
  }
}
class MyType {
  Spp.astMgr.insertMixin[MyMixin];
  // Now the class has the `print` method.
  ...
}
Mulitple mixins can also be merged into a single mixin usign the & operator, as in the following example:
def Mixin1: {
  handler this.print() {
    Console.print(this.toString());
  }
}
def Mixin2: {
  handler this.save(filename: String) {
    def content: String = this.toString();
    Fs.createFile(filename, content.buf, content.getLength());
  }
}
def Mixins: Mixin1 & Mixin2;
class MyType {
  Spp.astMgr.insertMixin[Mixins];
  // Now the class has the `print` and the `save` methods.
  ...
}

Build Library Reference


`Build` library allows the user to build an executable file for their program. The library contains the following items:

Exe Class

Allows the user to build an executable file for their program that works on the current operating system. This class needs `gcc` tools to be available on the operating system. Building an executable file is done following these steps:
  • Create an object from `Exe` class and pass to it a pointer to the source code element that we want to convert it to an executable file, in addition to the executable file's name that we want to build. It is possible to get the element's pointer using `~ast` command.
  • Adding any dependencies using `addDependency` function and pass to it a pointer to the module that the program depends on. Notice that adding dependency is required only when the module needs external libraries. The module set what external libraries it needs using `@deps` modifier, but if the module does not need external libraries then no need to add it as a dependency manually because the compiler does that automatically.
    It is also possible to give the function dependency file's name instead of giving it a pointer to a module, and in case of using dependencies files' names then it is possible to use `addDependencies` function to pass mutilple dependencies to the function at once.
  • Adding any other options that linker needs using `addFlag` or `addFlags` functions.
  • Creating an executable file by calling `generate` function.
The following example shows how to do these steps:
  def exe: Build.Exe(WidgetGuide.start~ast, "hello_world");
  exe.addDependency(Gtk~ast); // or exe.addDependency(String("libgtk...."));
  exe.addDependencies(Array[String]({ String("libcurl.so"), ... }));
  exe.addFlag("-Wl,-rpath,@executable_path");
  if exe.generate() {
    Srl.Console.print("Build complete.\n");
  } else {
    Srl.Console.print("Build failed.\n");
  };
The next code shows how libraries developers could add external dependencies information to their modules:
  @deps["libmyextlib.so"] module MyLib {
    ...
  };
Without adding `@deps` modifier, the `addDependency` function does nothing.

You can control the result of the build by changing the value of the `targetTriple` member of the `Exe` class, which allows targetting an OS different from the current OS. You can also specify a different linker from the default one by setting the `linkerFilename` of the `Exe` class. Through these two members you can target an OS different from the current OS. For example, you can build for Windows from a Linux machine by doing the following:

  def exe: Build.Exe(WidgetGuide.start~ast, "hello_world");
  exe.targetTriple = "x86_64-pc-win32";
  exe.linkerFilename = "x86_64-w64-mingw32-g++";
  exe.generate();
  

Wasm Class

This class is similar to `Exe` class and used in the same way but it generates web assemply code instead of a code that targets operating system and curren device architecture.
  def wasm: Build.Wasm(HelloWorld.start~ast, "hello_world");
  wasm.addDependency(String("stdlib.wasm"));
  wasm.addFlags({ String("--export=malloc"), String("--export=realloc") });
  if wasm.generate() {
    Srl.Console.print("Build complete.\n");
  } else {
    Srl.Console.print("Build failed.\n");
  };

genExeceutable Function

A helper function to create an executable file in one step. This function is useful in case building process does not need any dependencies or additional options. This function is just a interface for `Exe` class since internally it is using the `Exe` class to do the work.
  Build.genExecutable(start~ast, "hello_world");

genWasm Function

A helper function to create a web assemply file in one step. This function is useful in case building process does not need any dependencies or additional options. This function is just a interface for `Wasm` class since internally it is using the `Wasm` class to do the work.
  Build.genWasm(start~ast, "hello_world");

Zip Library Reference


`Zip` library contains the following functions:
  • extractFromFile
    func extractFromFile (
        filename: ptr[array[Char]], folderName: ptr[array[Char]],
        callback: ptr[func (ptr[array[Char]], ptr): Int], arguments: ptr
    ): Int;
    
    Extracts a zip compressed file to the specified directory.
  • compressToFile
    func compressToFile (
        filename: ptr[array[Char]], files: ptr[array[ptr[array[Char]]]], fileCount: Int,
        extractType: ptr[array[Char]]
    ): Int;
    
    Creates a zip compressed file containing the given files.

Apm Reference


`Apm` (Alusus Package Manager) is used to install and import packages directly from GitHub. It is possible to use this class from inside the program directly, or use the corresponding `apm` command line tool.
To get usage guideline for Apm from the command line you can write the following command:
$ apm help
Importing packages using Apm inside a program is done using `importFile` function:
import "Apm.alusus";
Apm.importFile("<author>/<pkg name>" [, "<filename>"]);
Apm.importFile("<author>/<pkg name>", { "<filename1>", "<filename2>", ... });
The second form of the function allows the user to include multiple files from the package at once. Using the second form to include multiple files is faster than calling the first form multiple times, because each call to this function will cause reading information from GitHub.
While developing packages usually the developer needs to try the package locally before pushing the changes to GitHub. Users can use the link command to link a certain package with a local folder, then Apm will use the local copy instead of downloading the GitHub copy. Link command must be executed inside the project directory that uses the package and has the following form:
$ cd <example_project_folder>
$ apm link <author>/<package_name>@<release> <path_to_local_package_copy>
It is possible to remove the link using `unlink` command, which has the following form:
$ cd <example_project_folder>
$ apm unlink <author>/<package_name>@<release>

Core Module Reference


`Core` module contains definitions for allowing interop between the Core and the user's program. This module contains definitions for some classes and functions that the Core uses while processing source code files and hence allows the programmer to deal directly with the Core and the data it creates. Not every element in the Core is exposed in the Alusus-based definitions, instead, only definitions useful to end users are exposed.

importFile Function

func importFile (filename: ptr[array[Char]]);
Include the file with the given name. This function do the work of `import` directive and does not differ from it except that it is able to include files dynamically, by using the name of a file generated dynamically at run time.

addLocalization Function

func addLocalization (
    locale: ptr[array[Char]],
    key: ptr[array[Char]],
    value: ptr[array[Char]]
);
Adds a translation to translations list for a specific language. If there is no previous entry for the required key then the translation will be added even if the current language is different from given translation language. The reason behind this is that having a text with another language is better than no text at all. But if there is an entry for the given key then the translation will be added only if it matches the current system language.
This function is used to add text to custom errors notifications, and key's value will be equal to error's code.

Type Info

In the module Core.Basic there exist definitions to allow objects to dynamically provide information about their types, i.e. during runtime. Most used classes in the Core and Spp contain type info. The following are the definitions that provide type information:
TiObjectFactory Class
Defined inside Core.Basic module
This class is used to enable the creation of new objects dynamically from a certain class's type info, which means that that type's info contains a reference to this factory. Therefore, if you have a reference to a type with information, then using this information you could access its factory and create new objects from the same type. `TiObjectFactory` contains pointers to the following functions:
  • createPlain
      def createPlain: ptr[@shared @no_bind function ():ref[TiObject]];
    
    Create an object and return a regular reference to it after initializing it.
  • createShared
      def createShared؛: ptr[@shared @no_bind function ():SrdRef[TiObject]];
    
    Create an object and return a `SrdRef` to it after initializing it.
  • initialize
      def initialize: ptr[@shared @no_bind function (ref[TiObject])];
    
    Intialize an object that already allocated in memory.
  • terminate
      def terminate: ptr[@shared @no_bind function (ref[TiObject])];
    
    Terminate an object without releasing its memory.
TypeInfo Class
Defined inside Core.Basic.
Holds type information for a specific class. This information includes type's name, the module that it belongs to, objects factory, and more. It contains the following variables:
  • typeName: (String)
    A string that holds type name without its full path.
  • typeNamespace: (String)
    A string that holds class' full path without the name of the class. If you have OuterModule.InnerModule.MyClass class then this variable will contain the value `OuterModule.InnerModule`.
  • packageName: (String)
    A string contains the name of the package that contains the class. For example, all known classes inside the core have `Core` as thier package name. Wheras classes deined inside Spp library have `Spp` as their package name.
  • url: (String)
    Package url on internet. Alusus does not deal with the url directly, instead it deal with it as an information to the use to distinguish the class from others in case of similar names.
  • uniqueName: (String)
    A string constructed from adding all previous information in one unique identifier.
  • baseTypeInfo: (ref[TypeInfo])
    A reference for base type info, in other words, it is a reference for type's info for the class that this class inherits from. Reference could be 0 in case of no base class.
  • objectFactory: (ref[TiObjectFactory])
    A reference to the factory that creates objects from this class.
TiObject Class
Defined inside the module Core.Basic.
The root for all classes with type info. Provides the ability to access type's info, in addition to accessing the interfaces that this class implements. Also, it provides the ability for dynamic casting. This class contains the following items:
  • getMyTypeInfo
      handler this.getMyTypeInfo (): ref[TypeInfo];
    
    Gives a reference to this type info.
  • isDerivedFrom
      handler this.isDerivedFrom (ref[TypeInfo]): Bool;
    
    Tell you whether this object is from the class with the given info, or from a class derived from that class.
  • getInterface
      handler this.getInterface (ref[TypeInfo]): ref[TiInterface];
    
    Receives a reference for interface info and retruns a reference to that interface if exists, else it return 0.
TiInterface Class
Defined inside module Core.Basic.
The root for all interfaces classes. It provides the ability to access the interface's type info, in addition to accessing the object that owns the interface. This class contains the following items:
  • getMyInterfaceInfo
      handler this.getMyInterfaceInfo (): ref[TypeInfo];
    
    Gives a reference to this inference info.
  • getTiObject
      handler this.getTiObject (): ref[TiObject];
    
    Returns the object that owns this inference.
getInterface Macro
Defined inside module Core.Basic.
A macro to ease getting an interface from a specific object. It is used in the following way:
  interface~no_deref = getInterface[obj, InterfaceType];
isDerivedFrom Macro
Defined inside module Core.Basic.
Check whether the given object is derived from the given class. It is used in the following way:
  if isDerivedFrom[obj, ObjType] { ... }
defDynCastedRef Macro
Defined inside module Core.Basic.
A macro used to shorten the operation of dynamically casting the class and storing the result in a new variable. For example:
  func test (parentObj: ref[ParentType]) {
    defDynCastedRef[childObj, parentObj, ChildType];
    if childObj~ptr != 0 childObj.someFunc(...);
  }

Basic Data Types with Type Info

Defined inside module Core.Basic.
Many classes are available that correspond to the basic types like integers and strings, but are derived from TiObject Class to provide runtime type info. These classes define equality and initialization operations in addition to the following items:
  • value
    The variable containing the original value in its original class.
  • create
      func create (v: BasicType): SrdRef[ObjType]
    
    Creates an object from this class with the given value and return a `SrdRef` to that object.
  • getTypeInfo
      func getTypeInfo (): ref[TypeInfo];
    
    This function returns a reference to the type's info, which means to the info of objects derived from this type.
The following example shows how to define and use these classes:
  def i: TiInt(45);
  printInt(i.value);
  i = 7;
  
  def o: ref[TiObject](i);
  if isDerivedFrom[o, TiInt] {
    def x: ref[TiInt](castRef[o, TiInt]);
    printInt(x.value);
  }
What follows show a list of the basic classes with an info.
  • TiInt
  • TiWord
  • TiFloat
  • TiBool
  • TiStr
  • TiPtr

Dynamic Data Access

The types used in Abstract Syntax Tree support a group of interfaces to allow traversing the tree dynamically. These interfaces allow querying the object's properties and accessing to those values to read or modify it.
Binding Interface
Defined inside module Core.Basic.
This interface allows the user to access the members of the object, and contains the following functions:
  • setMember
      handler this.setMember (memberName: ptr[array[Char]], value: ref[TiObject]);
      handler this.setMember (memberIndex: Int, value: ref[TiObject]);
    
    Set the value of an object's member, the first to specify the member based on its name, wheras the second is based on the order of the member amongs other members.
  • getMemberCount
      handler this.getMemberCount(): Word;
    
    Querying number of members for this object.
  • getMember
      handler this.getMember (memberName: ptr[array[Char]]): ref[TiObject];
      handler this.getMember (memberIndex: Int): ref[TiObject];
    
    Get the value of the member, with the given name in the first form, or the index in the second form.
  • getMemberNeededType
      handler this.getMemberNeededType (memberName: ptr[array[Char]]): ref[[TypeInfo];
      handler this.getMemberNeededType (memberIndex: Int): ref[TypeInfo];
    
    Get the type info for the specified member with given name in the first form, or the index in the second form.
  • getMemberKey
      handler this.getMemberKey (index: Int): String;
    
    Get the name of the memnber with the given index.
  • findMemberIndex
      handler this.findMemberIndex (name: ptr[array[Char]]): Int;
    
    Returns the index of the memnber with the given name, or -1 if not found.
Containing Interface
Defined inside module Core.Basic.
This interface is used to query other objects that this object contains, which means the other nodes that this class contains in the tree.
The difference between this interface and `Binding` interface is that the later is used to query the properties of the object itself which are considered a description of that node, while the first is used to to query other nodes separate from this node but associated to it. For example, if you have an object that represents binary operator between two values, then the operator type is considered to be a property and is queried using `Binding` interface, while the other two values are queried using `Containing` interface.
This interface contains the following functions:
  • setElement
      handler this.setElement (index: Int, value: ref[TiObject]);
    
    Set a new object to the element with the given index.
  • getElementCount
      handler this.getElementCount (): Word;
    
    Get the number of elements that this object contains.
  • getElement
      handler this.getElement (index: Int): ref[TiObject];
    
    Get the element with the given index.
  • getElementNeededType
      handler this.getElementNeededType (index: Int): ref[TypeInfo];
    
    Get the info of the required type for the element with the given index.
DynamicContaining Interface
Defined inside module Core.Basic.
It is derived from Containing and adds the ability to dynamically add and remove elements to the object. This interface is used with containers that could change their size, and contains the following functions in addition to the functions that are available in `Containing` interface:
  • addElement
      handler this.addElement (value: ref[TiObject]): Int;
    
    Adds new element to the end of the container, and returns the index of the new element.
  • insertElement
      handler this.insertElement (index: Int, value: ref[TiObject]);
    
    Adds new element to the container at the given index, after shifting all elements that are at that index or after it.
  • removeElement
      handler this.removeElement (index: Int);
    
    تزيل العنصر الذي عند التسلسل المعين ثم تزحف كل العناصر التي تلي ذلك التسلسل. Removes the element at the given index, and shifts all elements that are after that index.
  • getElementsNeededType
      handler this.getElementsNeededType (): ref[TypeInfo];
    
    Get the info of the required type for the container's elements without the need to set any specific element.
MapContaining Interface
Defined inside module Core.Basic.
It is derived from Containing and adds the ability to access elements by their names instead if their index. This interface contains the following functions:
  • setElement
      handler this.setElement (
        elementName: ptr[array[Char]], value: ref[TiObject]
      ): Int;
    
    Set a new value for the element with the given name and returns the index of that element.
  • getElement
      handler this.getElement (elementName: ptr[array[Char]]): ref[TiObject];
    
    Get the value of the element with the given name.
  • getElementNeededType
      handler this.getElementNeededType (
        elementName: ptr[array[Char]]
      ): ref[TypeInfo];
    
    Get the info for the required type of the element with the given name.
  • getElementKey
      handler this.getElementKey (index: Int): String;
    
    Get the name of the element with the given index.
  • findElementIndex
      handler this.findElementIndex (name: ptr[array[Char]]): Int;
    
    Get the index of the element with the given name, or -1 if not found.
DynamicMapContaining Interface
Defined inside module Core.Basic.
It is derived from MapContaining and adds the ability to dynamically remove or add new elements. This interface contains the following functions:
  • addElement
      handler this.addElement (
        name: ptr[array[Char]], value: ref[TiObject]
      ): Int;
    
    Adds ew element with the given name to the container and returns the index of that new element.
  • insertElement
      handler this.insertElement (
        index: Int, name: ptr[array[Char]], value: ref[TiObject]
      );
    
    Adds new element at the given index after shifting all elements that are at that index or after it.
  • removeElement
      handler this.removeElement (index: Int);
      handler this.removeElement (name: ptr[array[Char]]);
    
    Removes the element with the given index (the first form), or the given name (the second form).
  • getElementsNeededType
      handler this.getElementsNeededType (): ref[TypeInfo];
    
    Get the info of the required type for the container's elements without the need to specify a specific element.

AST

All classes that make AST are derived from class Node and support interfaces for dynamic data access, so it is possible at run time to traverse the AST dynamically that allows the user to read and create ASTs from inside their program which allows them to develop new grammatical structures to ease their work.
Node Class
Defined inside module Core.Data.
It is derived from TiObject. This is a root for other data classes like AST's classes. This class allows an object to access its owner.
  • owner: ref[Node]
    A reference to the owner of this object.
  • getTypeInfo
      func getTypeInfo (): ref[TypeInfo];
    
    This function returns a reference to this type info, which means the info of objects derived from this type.
Text Types
Defined inside module Core.Data.Ast.
It is derived from Node. These classes contain a single text section from the source code, without operators or expressions. The text section as one symbol so it could be an identifier, a number, or a lexer (a sequence of characters that compiler interprets). These classes contain the following items:
  • value: TiStr
    The string read from the source code.
  • getTypeInfo
      func getTypeInfo (): ref[TypeInfo];
    
    This function returns a reference to this type, which means the type of objects derived from this type.
  • create
      func create (value: ptr[array[Char]]): SrdRef[TheType];
    
    Creates an object from this class with the given value, and returns `SrdRef` to this object.
A list of text types:
  • Identifier
    Any identifier from the source code, like a variable name, a function name, or anyting similar to that.
  • StringLiteral
    Contains a string, which means a sequence of characters enclosed in double quotes. The value stored in this object does not include the double quote character, and does not include the whole composite character (like \) instead it only include the character itself. For example, when the user write \" then the value for this object will contain only " without \.
    In case the user splits their string into multiple parts (by closing the double quote and reopening it) then the value stored in that object will be a string containing all parts without spaces between them. For example, if the user enter "Hello " "World" then the value will be Hello World
  • CharLiteral
    Cotains a character. Which means the character enclosed in the source code between single quotes, but the value in this object will be without the quotes.
  • IntegerLiteral
    Contains an integer with the symbols related to Int class. For example, "456i32".
  • FloatLiteral
    Contains an integer with the symbols related to Float class. For example, "1.2f32".
Commands And Expressions Types
Defined inside module Core.Data.Ast.
It is derived from Node. The following is a list of types that represent expressions and commands. These classes do not have public functions other than getTypeInfo. To access their members the user can use the dynamic member access interfaces mentioned above.
  • Alias
  • Bracket
  • Bridge
    This class represent `use` expression that creates a bridge from a namespace which allows it to automatically access another namespace items.
  • Definition
  • GenericCommand
    A generic class used for holding unspecified command's information. It contains a string for the keyword and a dynamic list of data.
  • List
  • Map
  • MergeList
    A private class that tells the parser to merge its elements directly into the upper namespace instead of letting it be a sub-list inside the upper namespace.
  • ParamPass
    Represents the operation of passing data to an element, like calling a function, a macro, or a template.
  • Scope
  • Token
  • PrefixOperator
  • PostfixOperator
  • AssignmentOperator
  • ComparisonOperator
  • AdditionOperator
    For addition and subtraction operators.
  • MultiplicationOperator
    For multiplication and division operators.
  • BitwiseOperator
  • LogOperator
  • LinkOperator
    For the operators that link between an element and a member from it like dot operator, -> operator, or anything similar to that.
  • ConditionalOperator
  • Passage
    This type is used to refer to an AST element using its pointer instead of referring to that element using an identifier (the name of the element in the source code). This makes generating code dynamically easier in some cases.

Spp Module Reference


The module `Spp` contains functions to deal directly with `spp` library. Which is the library that is responsible for supporting standard programming paradigm. The elements of this module allow users to deal directly with the compiler from their programs.

buildMgr Object

This singleton object allows the user to deal with the executable code generator, and contains the following functions:
dumpLlvmIrForElement
  handler this.dumpLlvmIrForElement (element: ref[TiObject]);
`dumpLlvmIrForElement` function prints the intermediate code for a specific element from the source code. The printed intermediate code is a `LLVM IR` code. The function accepts one argument which is a pointer to the AST for that element. It is possible to get this pointer using `~ast` command as shown in the next example:
  Spp.buildMgr.dumpLlvmIrForElement(myFunc~ast);
buildObjectFileForElement
  handler this.buildObjectFileForElement (
    element: ref[TiObject],
    filename: ptr[array[Char]],
    targetTriple: ptr[array[Char]]
  ): Bool;
This function creates object file for the fiven element. Later it is possible to pass that file to the linker to create an executable file. The third argument is a value that determines the architecture used in the building. In case of passing 0 to this argument, the system's current architecture will be used. For example, to build an executable code with web assembly architecture we should pass "wasm32-unknown-unknown". For more information about this value, it is possible to refer to `LLVM` documentation.
This function returns 1 in case of success, 0 otherwise.
  Spp.buildMgr.buildObjectFileForElement(MyModule~ast, "output_filename", 0);
raiseBuildNotice
  func raiseBuildNotice (
    code: ptr[array[Char]], severity: Int, astNode: ref[TiObject]
  );
This function allows the programmer to raise a build notification programitically during preprocessing. This function takes 3 arguments:
  • code: Error notification code we want to show to the user. The list of available codes could be found inside `Notices_L18n` folder.
  • severity: A value that indicates the severity of the notification and could be one of the following values:
    • 0: The notification was for a root error and build could not proceed after it.
    • 1: An error notification but the compiling could proceed to find other errors if exists.
    • 2: An imporatant error notification that user should revise it.
    • 3: Non important error notification that could be ignored.
    • 4: An informative notification which is not an error.
  • astNode: A reference for the ast element related to the notification. The notification will point to the location of the source code that the element appears in.
Example:
  def errorCode: "SPPH1006";
  Spp.buildMgr.raiseBuildNotice(errorCode, 1, funcArgNode);
  // The above line will show an build error: Invalid function argument name.

grammarMgr Object

This singleton object allows the user to create new grammar rules for the language, and contains the following functions:
addCustomCommand
def TiObject: alias Core.Basic.TiObject;

handler this.addCustomCommand (
    identifier: ptr[array[Char]],
    grammarAst: ref[TiObject],
    handler: ptr[func (SrdRef[TiObject]): SrdRef[TiObject]]
);
This function is used to dynamically add a grammar rule for a new command to the language. This function accepts three arguments:
  • identifier: A string used as an identifier for the new rule. It is possible to pass any value for this identifier as long as we use letters and digits only and this identifier does not conflict with an existing identifier.
  • grammarAst: An ast that explains the new command. This tree takes the following form:
    ast {
        keywords: <keywords of the command>;
        args: <description of the args following the keyword>;
    }
    
    The command could have one keyword, or multiple ones (separated by | in the definition). The command could accepts no arguments, or accepts one argument or multiple arguments (separated by + in the definition). Every argument from these arguments consits of the path that leads to the parsing rule for the argument followed by * sign followed by parentheses that contain the lower and upper limits (respectively) for this argument appearence inside the command. Refer to the source code for Alsus rules for more information about the available commands that could be used as arguments.
  • handler: A function the accepts the ast that compiler created when parsing the source code. It processes it then then return an ast that inserted as a final result for parsing this command.
Example:
Spp.grammarMgr.addCustomCommand(
    "TestCommand",
    ast {
        keywords: "test_cmd";
        args: "module".Expression(0, 2) + "module".Set*(1,1);
    },
    func (args: SrdRef[TiObject]): SrdRef[TiObject] { ... }
);
The above example defines a new command that starts with the keyword `test_cmd` then followed by an argument with struct type that could appear once or twice consecutively or not at all, these expressions are followed by an expression with type set that must appear exactly once.
addCustomGrammar
def TiObject: alias Core.Basic.TiObject;

handler this.addCustomGrammar (
    identifier: ptr[array[Char]],
    baseIdentifier: ptr[array[Char]],
    grammarAst: ref[TiObject]
);
This function is used to dynamically add a new grammar rule to the language. It is different from `addCustomCommand` function in that the new rule is not restricted to be a command (it could be an expresssion or anything) and the new rule is derived from another rule. This function accepts three arguments:
  • identifier: A string that is used as an identifier for the new rule. It is possible any value to this identifier as long as it consists of letters and digits only, and does not conflict with an already existed identifier.
  • baseIdentifier: The identifier of the base rule that this rule is derived from it.
  • grammarAst: An ast that describes the changes that will applied on the new rule compared to the base rule. This tree takes the following form:
    ast {
        <path_to_value_to_update>: { <new_value> };
        ...
    }
    
    Rule's tree contains a set of inputs, each input is consist from the path to the value we want to change then the new value. The path is relative to the root of the rule.
In the next example a new rule will be created that is derived from the rule `FuncSigExpression` with one change which is changing the arguments of the sub-rule `BitwiseExp`. Look at Alusus rules to know the rules that could be derived from and what you need to change when you derive from them.
Spp.grammarMgr.addCustomGrammar(
    "ClosureSigExp",
    "FuncSigExpression",
    ast {
        BitwiseExp.vars: { enable: 1 }
    }
);

astMgr Object

This singleton object contains a functions for dealing with AST. It contains the following functions:
findElements
handler this.findElements (
    comparison: ref[Core.Basic.TiObject],
    target: ref[Core.Basic.TiObject],
    flags: Word
): Array[ref[Core.Basic.TiObject]];

handler this.findElements(
    comparison: ref[TiObject],
    target: ref[TiObject],
    flags: Word,
    modifierKwd: CharsPtr,
    kwdTranslations: ref[Map[String, String]]
): Array[ref[TiObject]];
Searches through the soruce code for elements that match the given search criteria. The first two arguments are references on two ASTs. The first one to an expression that represents the search criteria while the second is the ast that we want to search in it.
The third option could be one of the following values:
  • SeekerFlags.SKIP_OWNERS
    Tell the seeker to not search in namespaces that the current search location is in. For example, if you search in `Main.Sub` then it will look in `Sub` first then in `Main`, so if you add this option it will look only in `Sub`.
  • SeekerFlags.SKIP_USES
    Tells the seeker to ignore `use` statements while searching. Without this option, the seeker will search in namespaces where these expressions point to.
  • SeekerFlags.SKIP_CHILDREN
    Tells the seeker to not look in sub-namespaces. For example, if you search in `Main` namespace and this namespace contains the sub-namespace `Sub` then the seeker will search in `Main` then in `Sub` if you not give it this option. If you do it will look in `Main` only.
The fourth argument is the keyword of the modifier to filter by, if the user wants to filter the results also by modifiers. The fifth argument is a list of translations for modifier keywords to enable the user to filter by modifiers regardless of the localization of the source code.

The next example search in all functions inside a specific class:
  elements = Spp.astMgr.findElements(
    ast { elementType == "function" },
    MyClass~ast,
    Spp.SeekerFlags.SKIP_OWNERS | Spp.SeekerFlags.SKIP_USES | Spp.SeekerFlags.SKIP_CHILDREN
  );
It is possible to use search criteria by type or by the modifiers applied on the element. It is also possible to use a composite condition that contains `and` and `or` operators. Some examples of search criteria:
  elementType == "function" // search for functions
  elementType == "type" // search for types
  elementType == "module" // search for modules
  elementType == "var" // serach for variables
  modifier == "public" // search for elements with @public modifier
  elementType == "func" && modifier == "public" // search for functions with @public modifier
getDefinitionName
  handler this.getDefinitionName (
      element: ref[Core.Basic.TiObject]
  ): String;
Returns the name of the given element, which is the name of the definition owning it.
getModifiers
  handler this.getModifiers (
      element: ref[Core.Basic.TiObject]
  ): ref[Core.Basic.Containing];
Get the list of modifiers applied on the given element.
findModifier
  handler this.findModifier(
      modifiers: ref[Core.Basic.Containing],
      kwd: ptr[array[Char]]
  ): ref[Core.Basic.TiObject];
Find a modifier inside a list of modifiers. Seach is done using the keyword of the modifier. For example, to seach for `@expname` you need to pass the keyword `expname`.
findModifierForElement
  handler this.findModifierForElement(
    element: ref[Core.Basic.TiObject],
    kwd: ptr[array[Char]]
  ): ref[Core.Basic.TiObject];

  handler this.findModifierForElement(
    element: ref[Core.Basic.TiObject],
    kwd: ptr[array[Char]],
    kwdTranslations: ref[Map[String, String]]
  ): ref[Core.Basic.TiObject];
Find the modifier by the given keyword on the given element. The second form of this method translates modifiers against the given translations map before comparing them against the requested keyword.
getModifierKeyword
  handler this.getModifierKeyword(
    modifier: ref[Core.Basic.TiObject]
  ): Srl.String;
Returns the keyword for the given modifier.
getModifierParams
handler this.getModifierParams(
    modifier: ref[Core.Basic.TiObject],
    result: ref[Array[Core.Basic.TiObject]]
) => Bool;
Returns a list of all arguments passed to the modifier. This functions returns a boolean with value 1 on success, and 0 on failure.
getModifierStringParams
handler this.getModifierStringParams(
    modifier: ref[Core.Basic.TiObject],
    result: ref[Array[String]]
) => Bool;
Returns a list of all string arguments passed to the modifier. For example, if we have `deps["lib1", "lib2"]` modifier and we use this function, then we get from it a list for two elements, the first is "lib1", and the second is "lib2". This functions returns a boolean with value 1 on success, and 0 on failure.
getClassVars
handler this.getClassVars (parent: ref[TiObject]): Array[ref[TiObject]];

handler this.getClassVars (
    parent: ref[TiObject],
    kwd: ptr[array[Char]],
    kwdTranslations: ref[Map[String, String]]
): Array[ref[TiObject]];
Gets the list of variables of a given type. The second version brings only the variables carrying a specific modifier.
getClassVarNames
handler this.getClassVarNames (parent: ref[TiObject]): Array[String];

handler this.getClassVarNames (
    parent: ref[TiObject],
    kwd: ptr[array[Char]],
    kwdTranslations: ref[Map[String, String]]
): Array[String];
Gets the list of names of variables of a given type. The scond version brings only the names of variables carrying a given modifier.
getClassFuncs
handler this.getClassFuncs (parent: ref[TiObject]): Array[ref[TiObject]];

handler this.getClassFuncs (
    parent: ref[TiObject],
    kwd: ptr[array[Char]],
    kwdTranslations: ref[Map[String, String]]
): Array[ref[TiObject]];
Get the list of functions of a given type. The second version brings only the functions carrying a given modifier.
getClassFuncNames
handler this.getClassFuncNames (parent: ref[TiObject]): Array[String];

handler this.getClassFuncNames (
    parent: ref[TiObject],
    kwd: ptr[array[Char]],
    kwdTranslations: ref[Map[String, String]]
): Array[String];
Gets the list of names of functions of a given type. The second version gets only the names of functions having a specific modifier.
getFuncArgTypes
handler this.getFuncArgTypes (element: ref[TiObject]): ref[TiObject]
Gets the list of argument definitions for the given function.
getFuncArgType
handler this.getFuncArgType (element: ref[TiObject], index: Int): ref[TiObject]
Gets the definition of the given function's argument at the given index.
getSourceFullPathForElement
handler this.getSourceFullPathForElement(
    element: ref[Core.Basic.TiObject]
) => String;
Returns the full file name with the path for the source code file that contains the given element.
getSourceDirectoryForElement
handler this.getSourceDirectoryForElement(
    element: ref[Core.Basic.TiObject]
) => String;
Returns the full folder path which contains the source code file that contains the given element.
insertAst
handler this.insertAst(
    element: ref[Core.Basic.TiObject],
    interpolations: ref[Map[String, ref[Core.Basic.TiObject]]]
) => Bool;
handler this.insertAst(
    element: ref[Core.Basic.TiObject],
    interpolations: ref[Map[String, SrdRef[Core.Basic.TiObject]]]
) => Bool;
Inserts the given ast in the current position after applying the given interpolations on the given ast in a way similar to how macros work. The first argument to this function is the tree to be inserted, while the second argument holds the interpolations list.
The position where the ast will be inserted is the current position for the preprocessing, which means the position where `preprocess` expression that called `insertAst` appeared. The next example inserts 10 definitions to variables with type `Int`, its names are `n0` to `n9`:
  def i: Int;
  for i = 0, i < 10, ++i {
      def counter: TiStr = String.format("%i", i);
      Spp.astMgr.insertAst(
          ast { def n__counter__: Int },
          Map[String, ref[TiObject]]().set(String("counter"), counter)
      );
  }
buildAst
handler this.buildAst(
    element: ref[Core.Basic.TiObject],
    interpolations: ref[Map[String, ref[Core.Basic.TiObject]]],
    result: ref[SrdRef[Core.Basic.TiObject]]
) => Bool;
handler this.buildAst(
    element: ref[Core.Basic.TiObject],
    interpolations: ref[Map[String, SrdRef[Core.Basic.TiObject]]],
    result: ref[SrdRef[Core.Basic.TiObject]]
) => Bool;
handler this.buildAst(
    element: ref[Core.Basic.TiObject],
    interpolations: ref[Map[String, ref[Core.Basic.TiObject]]]
): SrdRef[Core.Basic.TiObject];
This function is similar to `insertAst` function, except that it creates ast and returns it to the caller instead of inserting it directly in the current position of preprocessing. The user could later insert the result in the current position using `insertAst`. The next example, is a modification of the previous one to use this function and also clearing the variables that it defines. The example creates a definition then use that definition as an interpolation in later call to `insertAst` function:
  def i: Int;
  for i = 0, i < 10, ++i {
      def counter: TiStr = String.format("%i", i);
      def result: SrdRef[TiObject];
      Spp.astMgr.buildAst(
          ast { def n__counter__: Int },
          Map[String, ref[TiObject]]().set(String("counter"), counter),
          result
      );
      Spp.astMgr.insertAst(
          ast {
              definition;
              n__counter = 0;
          },
          Map[String, ref[TiObject]]()
              .set(String("counter"), counter)
              .set(String("definition"), result)
      );
  }
insertCopyHandlers
handler this.insertCopyHandlers(obj: ref[TiObject]);

@member macro insertCopyHandlers [this];
Generates the copy operation and the copy constructor for the current type. It takes care of copying all member variables of that type. This macro must be used inside the body of a user type to generate the operations for that typel.
insertMixin
handler this.insertMixin(obj: ref[TiObject]);

@member macro insertMixin [this, target];
Inserts the given mixen into a class. The function inserts the mixin into the current location, so it must be used within a `preprocess` statement. The macro is just a helper that calls the function from a preprocess statement and gives it the AST ptr of the mixen. Visit mixins for more info.
getCurrentPreprocessOwner
handler this.getCurrentPreprocessOwner(): ref[Core.Basic.TiObject];
Returns a reference to the AST element that owns the currenly running preprocessing expression.
getCurrentPreprocessInsertionPosition
handler this.getCurrentPreprocessInsertionPosition(): Int;
Returns the position (inside the owner of the current preprocessing experssion) in which AST elements will be inserted when calling `insertAst` function.
getVariableDomain
handler this.getVariableDomain(element: ref[Core.Basic.TiObject]) => Int;
Returns a value that show the domain in which the given variable is defined. The result is one of the following values:
  def DefinitionDomain: {
      def FUNCTION: 0; // Var is a function local variable.
      def OBJECT:   1; // Var is a class member.
      def GLOBAL:   2; // Var is a global or shared variable.
  }
traceType
handler this.traceType(element: ref[Core.Basic.TiObject]) => ref[Spp.Ast.Type];
Traces the type that the given SPP element points to, and returns that type.
matchTemplateInstance
handler this.matchTemplateInstance(
    template: ref[Spp.Ast.Template],
    templateInput: ref[Core.Basic.TiObject],
    result: ref[SrdRef[Core.Basic.TiObject]]
) => Bool;
Returns the template instance that matches the given input. If no existing instance matches the input, then creates a new instance.
isCastableTo
handler this.isCastableTo(
    srcTypeRef: ref[Core.Basic.TiObject],
    targetTypeRef: ref[Core.Basic.TiObject],
    implicit: Bool
) => Bool;
Checks wether the given type (srcTypeRef) can be casted as a certian other type (targetTypeRef). The type arguments can be the type itself, or a reference to it. The third argument specifies wether to check for implicit casting or explicit casting.
computeResultType
handler this.computeResultType(
    element: ref[Core.Basic.TiObject],
    result: ref[ref[Core.Basic.TiObject]],
    resultIsValue: ref[Bool]
) => Bool;
This function computes the result from the given expression and returns it. The result could be a class or any other defintion like module or function. The last argument tells us if the result is a value (a variable from the given class for example) or the class itself (which means it tells you whether the expression is a definition for a class, or a variable from that class).
cloneAst
handler this.cloneAst(element: ref[Core.Basic.TiObject]): Srl.SrdRef[Core.Basic.TiObject] {
    return this.cloneAst(element, nullRef[Core.Basic.TiObject]);
}
handler this.cloneAst(
    element: ref[Core.Basic.TiObject], sourceLocationNode: ref[Core.Basic.TiObject]
): Srl.SrdRef[Core.Basic.TiObject];
This function clonse the given AST. The second form of this function allows the addition of a position in the source code to the source code positions stack related to the generated tree. The next argument in the next form is not the position in source code that we want to add to the stack, instead it is a the AST element we want to take the position from.
dumpData
handler this.dumpData(obj: ref[Core.Basic.TiObject]);
Prints the given ast to the console in a string format.
getReferenceTypeFor
handler this.getReferenceTypeFor(
    astType: ref[Core.Basic.TiObject]
): ref[Spp.Ast.ReferenceType];
Returns the reference type of the given type.
tryGetDeepReferenceContentType
handler this.tryGetDeepReferenceContentType(
    astType: ref[Spp.Ast.Type]
): ref[Spp.Ast.Type];
Returns the content type of the given type. If the given type is a reference then the function returns the content that this reference points to, otherwise it returns the type itself. If the given type is a reference to a reference then the function search recursively until it reaches a non-reference type and returns it.

AST Types

Types
defined inside module Spp.Ast.
  • Type
    Derived from Node.
    The root for all classes in Alusus.
  • DataType
    Derived from Type.
    The root for all data types.
  • IntegerType
    Derived from DataType.
  • FloatType
    Derived from DataType.
  • ArrayType
    Derived from DataType.
  • PointerType
    Derived from DataType.
  • ReferenceType
    Derived from DataType.
  • VoidType
    Derived from DataType.
  • UserType
    Derived from DataType.
  • FunctionType
    Derived from Type.
Operators
Defined inside module Spp.Ast.
Derived from Node.
  • AstRefOp
  • CastOp
  • InitOp
  • TerminateOp
  • NextArgOp
  • DerefOp
  • NoDerefOp
  • ContentOp
  • PointerOp
  • SizeOp
  • TypeOp
  • UseInOp
Statements
Defined inside Spp.Ast.
Derived from Node.
  • IfStatement
  • WhileStatement
  • ForStatement
  • ContinueStatement
  • BreakStatement
  • ReturnStatement
  • PreprocessStatement
  • PreGenTransformStatement
Others
Defined inside module Spp.Ast.
Derived from Node.
  • Template
  • TemplateVarDef
  • Block
  • Function
  • Macro
  • Module
  • Variable
  • ArgPack
  • AstLiteralCommand
  • ThisTypeRef

AST Processing


The ability for Alusus programs to read and create an AST at run time allows programmers to develop the language from inside their program by adding new rules to interpret the source code. In the next example, we define a macro that interprets the given expression to a string containing the condition statement, and replacing the variables by their real values at execution.
  import "Srl/Console";
  import "Srl/refs";
  import "Srl/System";
  import "Core/Data";
  import "Spp";

  use Srl;
  use Core.Basic;
  use Core.Data;
  use Core.Data.Ast;
  def TioSrdRef: alias SrdRef[Core.Basic.TiObject];

  func generateWhere (obj: ref[TiObject]): TioSrdRef {
      def result: TioSrdRef;
      if obj~ptr == 0 {
          System.fail(1, "generateWhere: obj is null.\n");
      }

      if isDerivedFrom[obj, ComparisonOperator] {
          def binding: ref[Binding](getInterface[obj, Binding]);
          def mapContaining: ref[MapContaining](getInterface[obj, MapContaining]);
          if !Spp.astMgr.buildAst(
              ast { String("{{name}}") + String(" {{op}} '") + val  + String("'") },
              Map[String, ref[TiObject]]()
                  .set(String("name"), mapContaining.getElement("first"))
                  .set(String("op"), binding.getMember("type"))
                  .set(String("val"), mapContaining.getElement("second")),
              result
          ) {
              System.fail(1, "generateWhere/ComparisonOperator: error\n");
          }
      } else if isDerivedFrom[obj, LogOperator] {
          def binding: ref[Binding](getInterface[obj, Binding]);
          def mapContaining: ref[MapContaining](getInterface[obj, MapContaining]);
          if !Spp.astMgr.buildAst(
              ast { (cond1) + String(" {{op}} ") + (cond2) },
              Map[String, ref[TiObject]]()
                  .set(String("cond1"), generateWhere(mapContaining.getElement("first")).obj)
                  .set(String("op"), binding.getMember("type"))
                  .set(String("cond2"), generateWhere(mapContaining.getElement("second")).obj),
              result
          ) {
              System.fail(1, "generateWhere/LogOperator: error\n");
          }
      } else if isDerivedFrom[obj, Bracket] {
          def mapContaining: ref[MapContaining](getInterface[obj, MapContaining]);
          if !Spp.astMgr.buildAst(
              ast { String("(") + (cond) + String(")") },
              Map[String, ref[TiObject]]()
                  .set(String("cond"), generateWhere(mapContaining.getElement("operand")).obj),
              result
          ) {
              System.fail(1, "generateWhere/Bracket: error\n");
          }
      } else {
          if !Spp.astMgr.buildAst(
              obj,
              Map[String, ref[TiObject]](),
              result
          ) {
              System.fail(1, "Failed to build condition.\n");
          }
      }
      return result;
  }

  macro where [condition] {
      preprocess {
          if !Spp.astMgr.insertAst(
              generateWhere(ast condition).obj
          ) {
              System.fail(1, "Failed to insert condition.\n");
          }
      }
  }

  func test {
      def firstName: String("Mohammed");
      def position: String("Engineer");
      def query: String = where[name == firstName && (pos == position || pos == "Lawyer")];
      Console.print("%s\n", query.buf);
      // Execution output: name == 'Mohammed' && (pos == 'Engineer' || pos == 'Lawyer')
  }
  test();