Andromeda

From HIVE
Jump to: navigation, search

Andromeda is an object-oriented, imperative scripting language that extends Blizzard's Galaxy language. It was designed to be an extension to Galaxy in the manner that vJASS was an extension to Warcraft III's JASS scripting language. Andromeda is implemented as a superset of Galaxy, meaning that all syntactically valid Galaxy code is also syntactically valid Andromeda code (the only exceptions are cases where the Galaxy code uses one of Andromeda's keywords). Moreover, Andromeda is compiled directly to Galaxy, allowing custom maps that use Andromeda to be played on Battle.net without the use of third-party mods.


Goals

Andromeda's main goals are to:

  • Maintain compatibility with native Galaxy code
  • Support object-oriented programming (OOP)
  • Support comprehensive and useful reporting of compile-time errors
  • Introduce common programming features absent from Galaxy
  • Reduce the need for unnecessary and boilerplate code
  • Produce optimized code in the target language when compiled
  • Provide simple and effective methods for code debugging

Design Paradigms

Andromeda is an object-oriented, procedural, and imperative scripting language with additional support for generic and (limited) reflexive programming. First and foremost, Andromeda adheres strictly to the tenets of Object-Oriented Programming (OOP). Procedural programming, while still supported, is included mainly for compatibility purposes with Galaxy. In Andromeda programming, an emphasis is placed on viewing the program as a collection of objects, each with its own state. Code is built around querying objects to determine their states and manipulating objects to change their states. This allows for the adoption or expansion of programming strategies such as:

  • Encapsulation
  • Modular Design (also known as Reusability)
  • Extensibility
  • Orthogonality

Language Features

Andromeda retains all of Galaxy's syntax features while adding new ones. Most of the new syntax is inspired by or directly borrowed from the Java programming language.

This section is meant to be an overview of the key features of Andromeda. It is not a manual on how to code in Andromeda nor is it a comprehensive language specification. For links to detailed language specifications and other Andromeda learning resources, visit the links section.

Source Code Structure

New Syntactical Features

Andromeda supports certain syntax found in C/C++ and Java that is missing in Galaxy, including:

  • Block commenting
  • Function/Method overloading
  • Multiple variable declaration on one line
  • Expanded implicit cast support
    • string to text
    • bool, int, fixed, char, byte to string or text when using the concatenate (+) operator
  • Local variable declaration freedom
  • {} Blocks
  • Postfix/Prefix increment/decrement operators (++ and --)
  • Using assignments as expressions: x = 5 + (y = 5); results in x being assigned the value of 10
  • for and for-each loops
  • Initializer (static) blocks

It also redefines some Galaxy constructs to make them more compatible with Andromeda style:

  • Forward declarations are no longer necessary (and discouraged)
  • The private keyword replaces the static keyword as a visibility modifier (use of the latter is possible, but discouraged)
  • Struct declarations no longer have to end with a semicolon
  • Reflexive programming using string literals to reference functions is discouraged. The .name field should be used instead to access a function/method's name.

Encapsulation

Classes

Classes are the largest and most important feature of Andromeda and lie at the core of Object-Oriented Programming. A class is an abstract representation of a group of related data and procedures, which are implemented as fields and methods, respectively. Classes can be created, or instantiated at runtime into objects that exist in memory. Objects are self-contained entities that are related to other objects of the same class through their common implementation. Like in Java, all objects must be dynamically-allocated; there is no support for stack allocation of objects.

Variables that are used to access objects are handled in the same way as Java references: they don't store the object itself but only act as references to it. These references are actually all int values, so variables of class types can be explicitly cast to int and vice versa. This also means that features in the native Galaxy API that store int can be used to store objects, as long as the original class type can be determined when the data is retrieved.

Because Andromeda does not have an automatic garbage collection system, objects have the potential to create memory leaks; an object will continue to occupy memory until it is explicitly destroyed. Because objects can be destroyed while still in scope, the possibility of referencing a deleted object arises and presents yet another chance for introducing bugs in code. In light of these issues, programmers must take care to adopt good memory management practices when using classes.

Because Galaxy does not support low-level dynamic (heap) allocation of any kind, the amount of memory to use for classes must be decided at compile-time. This leads to a limitation on the amount of instances a class can have active in memory at any one time. The default instance limit is 128, but Andromeda provides options to specify instance limits on a class-by-class basis in the code itself.

Polymorphism

Andromeda currently only supports inheritance polymorphism. Interface polymorphism is a major planned feature to be added at a later date. Under inheritance polymorphism, classes are allowed to extend other classes to declare what is known in computer programming as a is-a relationship. A class may extend at most one other class; multiple inheritance is not allowed. When class A extends class B, A becomes the subclass (also known as derived or child class) of B, which itself becomes the superclass (also known as base or parent class) of A. A subclass inherits all non-private members of its superclass. Subclasses also have the ability to override non-final inherited instance methods.

Andromeda Standard Library collections class hierarchy
A diagram of the class hierarchy of the collections classes in the Andromeda Standard Library. Lines connect superclasses (positioned higher vertically) to their subclasses. Light blue backgrounds represent abstract classes.

Polymorphism arises from the ability to treat objects of subclasses as if they were objects of the superclass type. Another way of saying this is that Andromeda's type system conforms to Liskov's substitution principle, which stated informally is:

If S is a subtype [subclass] of T, then S may be used anywhere T is needed without adverse effect.

Because subclasses can override inherited methods, Andromeda resolves all class method calls dynamically. That is, the version of the method that is called is the version belonging to the actual type of the object on which it was called, not on the type of the variable from which the object was referenced. For example, if a call to foo() is made to an object of type B, the subclass, referenced by a variable of type A, the superclass, it is B's version of foo() that will be called.

Andromeda also supports the implementation of abstract classes. These are classes with one more more abstract methods - methods that only declare their signatures and return types and leave out the method bodies. The idea behind abstract classes is to allow the programmer to specify only part of a class and leave other parts to be specified by subclasses that extend the abstract class. Because abstract classes are incomplete in a sense, they cannot be instantiated. Because interfaces are not yet available, abstract classes have been used in Andromeda in roles normally filled by interfaces, though there are limitations to this kind of workaround. For example, because abstract classes are still considered classes, the restriction on multiple inheritance applies and a subclass may only extend one abstract superclass.

While true polymorphism is reserved for classes only, a limited form is available to native types as well through the use of type extensions. Type extensions are an expansion of the typedef concept from Galaxy. There are two forms of type extensions: hierarchal and disjoint. Only the former allows for polymorphism. Hierarchal type extensions allow the programmer to declare his own types that extend native types or other type extensions. The same is-a relationship that applies to class inheritance applies here to hierarchal type extensions. Also like class inheritance, subtypes of type extensions may be treated as if they were the supertype. However, method calls to type extensions are resolved statically. That is, the version of the method that is called is the version belonging to the type of the variable from which the call was made.

The second kind of type extension - disjoint - does not allow for any polymorphism. It may only be used to extend native types and not other type extensions. Moreover, the subtype may not be treated as an instance of the supertype. Disjoint type extensions are meant to encapsulate native types rather than truly extend them in a polymorphic sense. Therefore, they are usually used to define constructs that can be modeled or represented by a native type, but does not share any behavior with that native type.

Generics

Andromeda supports generic programming with the implementation of generic types. Any class may be declared to be generic by postfixing angled brackets (<>) containing an arbitrary number of type parameters (also known as type arguments) to the class's name in its declaration. Only classes may be generic; unlike in C++, functions cannot be made generic. Two examples of generic class declarations are:

public class LinkedList<T> {
    //...
}

public class TreeMap<K,V> {
    //...
}

LinkedList is a generic type with one type parameter T and TreeMap is a generic type with two type parameters K and V. Generic classes must be parameterized upon instantiation. That is, each type parameter must be qualified with a type. This can be either a concrete type, another generic type, or even another type parameter (the last two are only valid if the instantiation itself occurs in a generic class). Unlike Java, Andromeda does not allow the instantiation of raw generic types - generic types that have not been parameterized. Currently, Andromeda allows classes, int, and type extensions of int to qualify type parameters. Wrapper classes must be used to parameterize generics with other native types (such as unit).

Aside from the restrictions described above, generic types are treated as if they were any other type. Generic classes may be extended by concrete or generic classes and vice versa. The Andromeda type system regards the same generic type with different qualified type parameters (LinkedList<Integer> vs. LinkedList<Fixed>) as different types, so they cannot be used interchangeably. While Andromeda allows inheritance polymorphism with respect to the generic types themselves, it does not allow it respect to the type parameters. In other words, assigning a Derived<A> to a variable of type Base<A> is allowed, but assigning a A<Derived> to a variable of type A<Base> is not.

When working with type parameters, no information about the type that eventually will be used to qualify the parameter can be determined from the type parameter itself. This is because Andromeda uses a strictly nominative type system wherein information about types is contained within the their names. Since type parameters are not actual type names but instead only placeholders, they impart no information. A consequence of this is that one may not call any methods on objects whose type is only described by a type parameter or use them where a concrete or generic type is expected (e.g. method arguments or return values) without explicit casts. It is also illegal to invoke the new and delete operators on a parameter type.

Enrichments

Enrichments are a major feature of Andromeda that allow the programmer to inject code into classes, native types, and type extensions that have already been defined elsewhere. This provides greatly expanded support for modular design, since optional code can be packaged into enrichments and injected into other types as needed instead of including all of it in the base type, leading to very bulky code, or resorting to unnecessarily complex inheritance trees.

In general, enrichments can declare and define any constructs that would be legal in the type that is being enriched. For enrichments of native types and type extensions, instance methods and static methods and fields may be declared but not instance fields. Members in enrichments cannot shadow or override members in the enriched type or in any other enrichment for that type; the compiler interprets it as duplicate definitions and will raise an error.

Because enrichments allow the addition of members to native types and their type extensions, they permit the application of object-oriented design paradigms to the native Galaxy API. The Andromeda Standard Library makes extensive use of enrichments for this purpose.

Type Safety

Libraries

See Also

Links