Genie Language - A brief guide
Contents
What is Genie?
Genie is a new programming language, in the same vein as Vala, that allows for a more modern programming style while being able to effortlessly create and use gobjects natively
The syntax is designed to be clean, clear and concise and is derived from numerous modern languages like Python, Boo, D language and Delphi.
Genie is very similar to Vala in functionality but differs in syntax allowing the developer to use cleaner and less code to accomplish the same task.
Like Vala, Genie has the same advantages:
- Programs written in Genie should have have similar performance and resource usage to those written directly in Vala and C
- Genie has none of the bloat and overhead that comes with many other high level languages which utilize a VM (Eg Python, Mono, et al)
- Classes in Genie are actually gobjects so Genie can be used for creating platform code like widgets and libraries where gobjects are required for binding to other languages.
Hello World
As Hello World is the de facto first sample program, here is Genie's in all its glory:
init
print "Hello World"As you can see, Genie is pretty clean and concise so the above example wont need much explaining...
An init block declared outside of a class or struct is equivalent to a "main" function in c and only one of these may be present. Here any code that needs to be run at startup should be present.
Block indentation in Genie is by default using tabs so in the above example a tab must be enetered prior to the keyword "print"
Its also possible to tell it to use a certain number of spaces in lieu of a tab by ensuring the first line of the source file conatins the following attribute:
[indent=2] init print "Hello World"
That attribute tells Genie to ignore tabs and use 2 spaces to signify a block indentation. An indent=0 means use a tab
Code examples on this page will always use [indent=4] as this wiki converts tabs to spaces in code examples which means no tabs would be copied in a cut and paste of the code.
Provided you have latest Vala svn or version 0.3.3 or greater installed, you can compile it by simply issuing the following:
$ valac hello.gs $ ./hello
If you give valac the -C switch, it will also create two files called hello.h and hello.c.
Basic Concepts
Files
Genie code must be written in files with the *.gs extensions.
When you want to compile Genie code, you give the compiler a list of the files required, and Genie/Vala compiler will work out how they fit together.
Syntax
Genie's syntax mixes features of several high level languages including Python, Boo, Delphi and D
Genie is case sensitive as a result of it being compiled into c code - so be careful when mixing case in your code.
An identifier is defined by its name and its type, e.g. i:int meaning an integer called i. In the case of value types this also creates an object of the given type. For reference types these just defines a new reference that doesn't initially point to anything.
Genie has a mechanism called Type Inference, whereby a local variable may be defined using var instead of giving a type, so long as it is unambiguous what type is meant.
var i = 3
var
a = "happy"
b = "sad"
c = "ambivalent"
for var I = 1 to 10
print "looping" var can be used for type inferencing in both single lines and blocks as well as "for" statement initializers.
Reference types are instantiated using the new operator and the name of a construction method, which is usually just the name of the type, e.g. var o = new Object() creates a new Object and makes o a reference to it.
Prefixing symbols with @ let you using reserved keywords. This is mainly useful for bindings. You can use an argument @for for example.
Genie also allows comments in code in two different ways.
// Comment continues until end of line /* Comment lasts between delimiters */
Operators:
/* assignment */
a = 2
/* arithmetic */
a = 2+2 // 4
a = 2-2 // 0
a = 2/2 // 1
a = 2*2 // 4
a = 2%2 // 0
a++ // equivalent to a = a + 1
a-- // equivalent to a = a - 1
a += 2 // equivalent to a = a + 2
a -= 2
a /= 2
a *= 2
a %= 2
/* relational */
a > b
a < b
a >= b
a <= b
a is not b // not equal eg if a is not 2 do print "a is not 2"
a is b // equality eg if a is 2 do print "a is 2"
/* logical */
a and b
a or b
not (b)
if (a > 2) and (b < 2)
print "a is greater than 2 and b is less than 2"
/* bitwise operators (same as C syntax) */
|, ^, &, ~, |=, &=, ^=
/* bit shifting */
<<=, >>=
/* Object related */
obj isa Class
Control Flow statements:
if a > 0 print "a is greater than 0" else if a is 0 print "a is equal to 0" else print "a is less than 0"
executes a particular piece of code based on a set of conditions. The first condition to match decides which code will execute, if a is greater than 0 it will not be tested whether it is less than 0. Any number of else if blocks is allowed, and zero or one else blocks.
All control flow statements can take blocks or be single line. Single line ones must always use the "do" keyword between the condition and the statements to be executed.
if a > 0 do print "a is greater than 0"
while a > b a--
while a > b do a--
will decrement "a" repeatedly, checking before each iteration that "a" is greater than "b".
for var i = 1 to 10 print "i is %d", i
will initialize "i" to 1 and then call the print statement repeadetly whilst incrementing "i" until it reaches a vlaue of 10
for s in args do print s
will print out each element in the args string array or another iterable collection. The meaning of iterable will be described later.
All of the three preceding types of loop may be controlled with the keywords break and continue. A break instruction will cause the loop to immediately terminate, while continue will jump straight to the next part of the iteration.
case a
when 0,1,2
print "a is less than 3"
when 3
print "a is 3"
default
print "a is greater then 3"a case..when statement runs a section of code based on the value passed to it. In Genie (unlike C) there is no fall through between cases, as soon as a match is found no more will be checked.
Data Types
There are broadly two types of data in Genie: Reference types and Value types. These names describe how instances of the types are passed around the system - a value type is copied whenever it is assigned to a new identifier, a reference type is not copied, instead the new identifier is simply a new reference to the same object.
Genie's value types are the basic simple data types, and the compound type "struct". The simple types are:
- char, uchar
- int, uint
- float, double
- bool (boolean)
- unichar (Unicode character)
- string
The reference types are all types declared as a class, regardless of whether they are descended from GLib's Object. Genie will ensure that when you pass an object by reference the system will keep track of the number of references currently alive in order to manage memory for you. If you define your own classes as being descending from GLib's Object, your types will do this as well. Also all arrays are created as reference types, as is the string type, representing a UTF-8 encoded string.
Nullable Types
By default, Genie will make sure that all reference point to actual objects. This means that you can't arbitrarily assign null to a variable. If it is allowable for a reference to be null you should declare the type with a ? modifier. This allows you to assert which types may be null, and so hopefully avoid related errors in your code.
This modifier can be placed both on parameter types and return types of functions:
/* allows null to be passed as param and allows null to be returned */
def fn_allow_nulls (string? param) : string?
return param
/* attempting to pass null as param will lead to that function aborting. Likewise it may not return a null value */
def fn_no_null (string param) : string
return param These checks are performed at run time. For release versions of a program, when the code is fully debugged, the checks can be disabled. See the valac documentation for how to do this.
Weak References
Normally when creating an object in Genie you are returned a reference to it. Specifically this means that as well as being passed a pointer to the object in memory, it is also recorded in the object itself that this pointer exists. Similarly, whenever another reference to the object is created, this is also recorded. As an object knows how many references there are to it, it can automatically be removed when needed. This is the basis of Genie's memory management.
Weak References conversely are not recorded in the object they reference. This allows the object to be removed when it logically should be, regardless of the fact that there might be still references to it. The usual way to achieve this is with a function defined to return a weak reference, e.g.:
class Test
Object o
def get_weak_ref () : weak Object
o = new Object()
return oWhen calling this function, in order to collect a reference to the returned object, you must expect to receive a weak reference:
o : weak object = get_weak_ref ()
The reason for this seemingly overcomplicated example because of the concept of ownership. If the Object "o" was not stored in the class, then when the function "get_weak_ref" returned, "o" would become unowned (i.e. there would be no references to it). If this were the case, the object would be deleted and the function would never return a valid reference. If the return value was not defined as weak, the ownership would pass to the calling code, but a weak reference cannot transfer ownership.
Weak references play a similar role to pointers, described next. They are however much simpler to use, and can be easily combined with normal references, making them useful more easily and more often. They aren't though that widely used, and probably don't need to be.
Ownership transfer
'#' is used to transfer ownership.
As a suffix of a parameter (or property) type. it means that ownership of the object is transferred. As an operator, it can be used to avoid copies of non-reference counting classes. e.g.
var s = "hello" /* s will be null when owner is transferred */ t : string = #s print t
This means that s will be set to null and t inherits the reference/ownership of s.
Pointers
Pointers are Genie's way of allowing manual memory management. Normally when you create an instance of a type you receive a reference to it, and Genie will take care of destroying the instance when there are no more references left to it. By requesting instead a pointer to an instance, you take responsibility for destroying the instance when it is no longer wanted, and therefore get greater control over how much memory is used.
This functionality is not necessarily needed most of the time, as modern computers are usually fast enough to handle reference counting and have enough memory that small inefficiencies are not important. The times when you might resort to manual memory management are:
- When you specifically want to optimise part of a program.
- When you are dealing with an external library that does not implement reference counting for memory management (probably meaning one not based on gobject.)
In order to create an instance of a type, and receive a pointer to it you must add the * suffix to the type declaration :
o : object* = new Object
In order to access members of that instance:
o->function_1 o->data_1
In order to free the memory pointed to:
delete o;
Objects
Genie is an Object Orientated language although its quite possible to do code without objects. In most cases you will want to use objects to build your application as they have a number of benefits.
An object consists of a class which can inherit from another class. Classes may contain fields, properties, methods and events all of which can be made publicly available to other classes or can be private and internal to the class
All members of a class are always publicly accessible unless they start with an underscore or use the private keyword modifier
A simple example:
class Foo : Object
/* property declaration */
prop p1 : int
/* method declaration */
def bar (name : string)
print "hello %s p1 is %d", name, p1
/* method with return value */
def bar2 () : string
return "bar2 method was called"The above example shows that class Foo is a subclass of object (which means it inherits all the features of that class). Subclassing in Genie is pretty much the same as in other OO languages except that multiple inheritance is not supported. Although a class in Genie can only descend from one other class, it can implement many interfaces - this will be discussed later.
classes are instantiated using the new operator. EG:
init
var foo = new Foo()There are certain special and optional method blocks which are applicable to classes -
An init block is used to wite code that pertains to the initlization of the class - only one of these may be present in any class declaration. An init block declared outside of a class is equivalent to a main function in c and is used to perform initialization of the entire application. init blocks do not have any parameters
A final block is used to perform any finalization of the class when an object instance is destroyed. Final blocks do not take parameters
A construct block is used to define a creation method which requires parameters at construction time when being instantiated via the new operator. Creation methods are limited to setting the properties of the class and may perform no other task (an init block should be used to perform any other type of initialization). A class can have many creation methods with either different names or different parameters. A default creation method without any parameters is always available if no explicit creation method is defined.
EG
[indent=4]
init
/* different creation methods can be specified (IE thoses defined with "construct" keyword) where they take different params
var foobar = new Foo (10)
/* this creation method is same as above but is named differently */
var foobar2 = new Foo.with_bar (10)
class Foo : Object
prop a : int
init
print "foo is intitialized"
final
print "foo is being destroyed"
/* only class properties may be set in creation methods */
construct (b : int)
a = b
/* only class properties may be set in creation methods */
construct with_bar (bar : int)
a = bar
Methods
Methods are actions on a class or object
Methods always start with the keyword def followed by optional modifiers (private, abstract, inline, extern, virtual, override), the name of the method, a list of method parameters (if any) and lastly, if it returns a value, the type of the return value.
Heres an example
[indent=4]
init
var f = new Foo ()
print f.bar ("World")
class Foo : Object
def static bar (name : string) : string
return "Hello " + name The above defines a method called bar which takes a string parameter called name and returns a string.
All methods, properties and events can also take modifiers to define further options. Modifiers are always placed between the def keyword and the method name. In the above example method_name is defined using the 'virtual' modifier. A full explanation of these modifiers are as follows:
private - indicates that this method/property/event is not accessible from outside the defining class. It is not necessary to include this modifier if its name starts with an underscore as anything that starts with that is always deemed private
abstract - indicates that the method body is not defined in the current class but any subclass must implement this method. This is very useful when defining interfaces which classes can implement
extern - indicates that this method is defined outside of genie/vala sources and is often implemented in C. This handy feature allows methods to be defined in C (or assembler) where necessary - c files to be included in the final executable have to be passed to valac in order for this to work.
inline - hints to the compiler that it should inline this method to improve performance. This is useful for very small methods
virtual - indicates that this method may be overriden in subclasses
override - indicates to override the super class virtual method of the same name and replace it with the new definition. If no method with the virtual modifier exists in the superclass then the compiler will signal an error.
static - indicates a method that can be run without needing an instance of the class. Static methods never change the state or properties of a class instance as a result of this.
A method in Genie is passed zero or more parameters. The default behaviour when a method is called is as follows:
- Any value type parameters are copied to a location local to the function as it executes.
- Any reference type parameters are not copied, instead just a reference to them is passed to the function.
This behaviour can be changed with the modifiers 'ref' and 'out'.
- 'out' from the caller side
- you may pass an uninitialized variable to the method and you may expect it to be initialized after the method returns
- 'out' from callee side
- the parameter is considered uninitialized and you have to initialize it
- 'ref' from caller side
- the variable you're passing to the method has to be initialized and it may be changed or not by the method
- 'ref' from callee side
- the parameter is considered initialized and you may change it or not
Heres an example:
[indent=4]
init
var
a = 1
b = 2
c = 3
Foo.bar (a, out b, ref c)
print "a=%d, b=%d, c=%d", a,b,c
class Foo : Object
def static bar (a:int, out b: int, ref c: int)
a = 10
b = 20
c = 30The treatment of each variable will be:
- "a" is of a value type. The value will be copied into a new memory location local to the function, and so changes to it will not be visible to the caller.
"b" is also of a value type, but passed as an out parameter. In this case, the value is not copied, instead a pointer to the data is passed to the function, and so any change to the function parameter will be visible to the calling code.
- "c" is treated in the same way as "b", the only change is in the signalled intent of the function.
Properties
Genie classes can also have properties which are identical to gobject properties
Within a class, properties can be defined in various ways, including:
class Foo : Object
prop name : string
prop readonly count : int
[Description(nick="output property", blurb="This is the utput property of the Foo class")]
prop output : string
get
return "output"
set
_name = value In the simplest case, the name property is declared without any get/set methods so Genie automatically creates a private field called _name (it always generates a field with the same name as the property but with an underscore prepended) and performs automatic get/set operations for you
Properties that can only be read and never written must use the readonly keyword as in the count property above
Properties, as in the last case, can of course have user defined get and set definitions as well as gobject short and long descriptions (via the attribute)
In order to use these properties, refer to them in the same way as public members of the class, i.e. (assuming a class called "T"):
var t = new T()
t.name = "my name"
Events
Events (aka Signals) are an intrisic feature of GObjects and all classes which are derived from Object can have them. An event is assigned handlers (code blocks) and when the event is triggered those event code blocks are executed
An event is defined as a member of a class, and appears similar to a method with no body. Event handlers can then be added to the event using the special += operator.
[indent=4]
init
var f = new Foo()
/* set event handler */
f.my_event += def (t, a)
print "event was detected with value %d", a
/* fire the event */
f.my_event (5)
class Foo : Object
event my_event (a : int)Also depicted above is a closure which provides the event handler. A closure allows a code block to be inlined into the source. The alternative to a closure is to provide a callback function for the event handler.
The reason there are two arguments to the handler (t and a) is that whenever a signal is emitted, the object on which it is emitted is passed as the first argument to the handler. The second argument is the one that the signal says it will provide.
We trigger the event by calling it like a method on the class.
NB: Currently Genie doesn't support signals with return values + all events are never private
Interfaces
A class in Genie may implement any number of interfaces. Each interface is a type, much like a class, but one that cannot be instantiated. By "implementing" one or more interfaces, a class may declare that its instances are also instances of the interface, and therefore may be used in any situation where an instance of that interface is expected.
The procedure for implementing an interface is the same as for inheriting from classes with abstract methods in - if the class is to be useful it must provide implementations for all methods that are described but not yet implemented.
A simple interface definition looks like:
interface ITest
prop abstract data : int
def abstract fn ()This code describes an interface "ITest" which contains two members. "data" is a property, as described above, except that it is declared abstract. Genie will therefore not implement this property, but instead require that classes implementing this interface have a property called "data" that has both get and set accessors - it is required that this be abstract as an interface may not have any data members. The second member "fn" is a method and as above any class implementing this interface must also have a method called fn with the same params and return value
A possible implementation of this interface is:
[indent=4]
init
var f = new Foo ()
f.fn ()
interface ITest
prop abstract data : int
def abstract fn ()
class Foo : Object implements ITest
prop data : int
def fn ()
print "fn"Interfaces in Genie may also inherit from other interfaces, for example it may be desirable to say that a "List" interface is also a "Collection" interface, and so the first should inherit from the second. The syntax for this is exactly the same as for describing interface implementation in classes:
interface List : Collection
However this definition of "List" may not now be implemented in a class without "Collection" also being implemented, and so Genie enforces the following style of declaration for a class wishing to implement "List", where all implemented interfaces must be described:
class ListCLass : Object implements Collection, List
Special Operators
You can check if a object is of a given Object type by using the isa operator:
init
var o = new list of string
if o isa Object
print "a list of string is an object"
if o isa list of Object
print "a list of string is a list of Object"
if o isa Gee.Iterable
print "a list of string implements Iterable"
Collection types
Genie has a number of colleciton types built in including arrays, lists and dicts.
Gee provides the support for list and dict types in Genie so if you make use of these you will need to have libgee installed (as will anyone who wants to run your compiled programs). if you dont make use of list and dicts then you do not need this library
Arrays
An array is an ordered collection of items of a fixed size. You cannot change the size of an array so additional elements cannot be added or removed (although exisitng elements can have their value changed)
An array is created by using "array of type name" followed by the fixed size of the array e.g. var a = new array of int[10] to create an array of 10 integers. The length of such an array can be obtained by the length member variable e.g. var count = a.length.
Arrays can take initilializers too, in which case theres no need to specify a length :
tokens : array of string = {"This", "Is", "Genie"}Arrays can be constant too:
const int_array : array of int = {1, 2, 3}Individual elements in an array can be accessed by their position index. They can also be iterated over using the standard for..in statement. Heres an example demonstrating this:
[indent=4]
const int_array : array of int = {1, 2, 3}
init
tokens : array of string = {"This", "Is", "Genie"}
var sa = new array of string[3]
var i = 0
for s in tokens
print s
sa[i] = s
i++
sa[2] = "Cool"
for s in sa
print s
for i in int_array
print "number %d", iYou can have arrays of any type including nested types. Arrays of structs are also possible:
[indent=4]
init
var f = new Foo ()
try
var opt_context = new OptionContext ("- Test array of structs")
opt_context.set_help_enabled (true)
opt_context.add_main_entries (f.options, null)
opt_context.parse (ref args)
except e:OptionError
stdout.printf ("%s\n", e.message)
stdout.printf ("Run '%s --help' to see a full list of available command line options.\n", args[0])
class Foo : Object
[NoArrayLength ()]
test_directories : static array of string
const options : array of OptionEntry = {{ "testarg", 0, 0, OptionArg.FILENAME_ARRAY, ref test_directories, "test DIRECTORY", "DIRECTORY..." }, {null}}
Lists
Lists are ordered collections of items, accesible by numeric index. They can grow and shrink automatically as items are added/removed. In essence a list is really a dynamic array.
Lists are generic in nature and use the same syntax as other generics. A new list is therefore created in the same way:
var l = new list of string
the above creates a string list but as with generics all other types are supported too. Elements in a list can be accessed and changed just like it was an array. As lists are also iterables 'for..in' can be used to iterate over them too.
[indent=4]
init
/* test lists */
var l = new list of string
l.add ("Genie")
l.add ("Rocks")
l.add ("The")
l.add ("World")
for s in l
print s
print " "
l[2] = "My"
for s in l
print s
print " "
Dicts
Dicts (also known as dictionaries) are an unordered collection of items accesible by index of arbitry type.
They can be used as lookup tables and to quickly map values of one types to another values of the same or another type
In other languages dicts may be known as hash maps or hash tables.
dicts typically consist of key/value pairs and when creating a new one you need to specify the key and value types :
var d = new dict of string,string
The above example creates a dict whose key is a string and whose value is also a string
The types can be different of course. Say we want to implement a phone book then we would have the key as a string (name) and the phone number as an int. We would create this using:
var d = new dict of string,int
Dicts, like lists, can be accessed by key index and its keys and values can also be iterated over using the standard for..in statement. You can add new entires by using the add method or more easily by using d[key] = value
Here is a full example:
[indent=4]
init
/* test dicts */
var d = new dict of string,string
/* add or change entries with following */
d["Genie"] = "Great"
d["Vala"] = "Rocks"
/* access entires using d[key] */
for s in d.get_keys ()
print "%s => %s", s, d[s]
Other Types and Containers
Delegates
delegate DelegateType(a:int)
a delegate is a type of function pointer, allowing chunks of code to be passed around like objects. The example above defines a delegate called "DelegateType" which represents a function taking an int and not returning a value. Any function with such a signature may be used as a "delegate_name" as shown in the following sample:
delegate DelegateType (a : int) : bool
def f1 (a:int) : bool
print "testing delegate value %d", a
return true
def f2 (d:DelegateType, a:int)
var c = d (a)
// to execute it
f2 (f1, 5)this code will execute the function "f2", passing in a pointer to function "f1" and the number 5. "f2" will then execute the function "f1", passing it the number.
Namespaces
namespace MyNameSpace blah...
everything within the indent of this statement is in the namespace "MyNameSpace" and must be referenced as such. Any code outside this namespace must be either used qualified names for anything within the name of the namespace, or be in a file with an appropriate using declaration. Namespaces may contain any code except using statements.
Namespaces can be nested, either by nesting one declaration inside another, or by giving a name of the form "NameSpace1.NameSpace2".
Several other types of definition can declare themselves to be inside a namespace by following the same naming convention, e.g. class NameSpace1.Test.... Note than when doing this, the final namespace of the definition will be the one the declaration is nested in plus the namespaces declared in the definition.
To use a namespace, use the uses statement:
uses
Gtk
Enums
enum MyEnum
FirstValue
SecondValuedefines an enum type - this type is an enumberable sequence of values starting from zero. In this example FirstValue has the value 0 and SecondValue has the value 1.
It is possible to give specific integer values to any entity in an enum:
enum MyEnum
FirstValue = 1
ThirdValue = 3enum can be referred using its type, e.g. MyEnum.FirstValue.
Structs
A struct type, i.e. a compound value type. A Genie struct may have methods in a limited way and may also have private data.
struct StructName
a : string
i : int
Error Handling
GLib has a system for managing runtime exceptions called GError. Genie translates this into a form familiar to modern programming languages, but its implementation means it is not quite the same as in Java or C#. It is important to consider when to use this type of error handling - GError is very specifically designed to deal with recoverable runtime errors, i.e. factors that are not known until the program is run on a live system, and that are not fatal to the execution. You should not use GError for problems that can be foreseen, such as reporting that an invalid value has been passed to a function. If a function, for example, requires a number greater than 0 as a parameter, it should fail on negative values using the debugging utils such as assert.
Using exceptions is a matter of:
1) Declaring that a function may raise an error:
def fn (s:string) raises IOError
2) Throwing the error when appropriate:
if not check_file (s)
raise new IOError.FILE_NOT_FOUND ("Requested file could not be found.")3) Catching the error from the calling code:
try
fn("home/jamie/test")
except IOError ex
print "Error: %s", ex.messageAll this appears more or less as in other languages, but defining the types of errors allowed is fairly unique. Errors have three components, known as "domain", "code" and message. Messages we have already seen, it is simply a piece of text provided when the error is created. Error domains describe the type of problem, and equates to a subclass of "Exception" in Java or similar. In the above examples we imagined an error domain called "IOError". The third part, the error code is a refinement describing the exact variety of problem encountered. Each error domain has one or more error codes - in the example there is a code called "FILE_NOT_FOUND".
The way to define this information about error types is related to the implementation in glib. In order for the examples here to work, a definition is needed such as:
exception IOError
FILE_NOT_FOUND
FILE_NO_READ_PERMISSION
FILE_IS_LOCKEDWhen catching an error, you give the error domain you wish to catch errors in, and if an error in that domain is thrown, the code in the handler is run with the error assigned to the supplied name. From that error object you can extract the error code and message as needed. If you want to catch errors from more than one domain, simply provide extra catch blocks. There is also an optional block that can be placed after a try and any catch blocks, called finally. This code is to be run always at the end of the section, regardless of whether an error was thrown or any catch blocks were executed, even if the error was in fact no handled and will be thrown again. This allows, for example, any resources reserved in the try block be freed regardless of any errors raised. A complete example of these features:
[indent=4]
exception ErrorType1
CODE_1A
exception ErrorType2
CODE_2A
init
try
Test.catcher ()
except ex : ErrorType1
print ex.message + " was caught"
class Test : Object
def static thrower () raises ErrorType1, ErrorType2
raise new ErrorType2.CODE_2A ("Error CODE 2A was raised")
def static catcher () raises ErrorType1
try
thrower ()
except ex:ErrorType2
print ex.message
finally
print "all exceptions handled"
This example has two exceptions, both of which can be thrown by the "thrower" function. Catcher can only throw the second type of error, and so must handle the first type if "thrower" throws it.
Generics
Genie includes a runtime generics system, by which a particular instance of a class can be restricted with a particular type or set of types chosen at construction time. This restriction is generally used to require that data stored in the object must be of a particular type, for example in order to implement a list of objects of a certain type. In that example, Genie would make sure that only objects of the requested type could be added to the list, and that on retrieval all objects would be cast to that type.
In Genie, generics are handled while the program is running. When you define a class that can be restricted by a type, there still exists only one class, with each instance customised individually. This is in contrast to C++ which creates a new class for each type restriction required - Genie's is similar to the system used by Java. This has various consequences, most importantly: that static members are shared by the type as a whole, regardless of the restrictions placed on each instance; and that given a class and a subclass, a generic refined by the subclass can be used as a generic refined by the class.
The following code demonstrates how to use the generics system to define a minimal wrapper class and create two instances of different types
[indent=4]
init
var string_wrapper = new Wrapper of string
string_wrapper.set_data ("Hello World")
var s = string_wrapper.get_data ()
print s
var int_wrapper = new Wrapper of int
int_wrapper.set_data (6)
var data = int_wrapper.get_data ()
print "test int is %d", data
var test_wrapper = new Wrapper of TestClass
var test = new TestClass
test.accept_object_wrapper (test_wrapper)
class Wrapper of G : Object
_data : G
def set_data (data : G)
_data = data;
def get_data () : G
return _data
class TestClass : Object
def accept_object_wrapper (w : Wrapper of Object)
print "accepted object"
This "Wrapper" class must be restricted with a type in order to instantiate it - in this case the type will be identified as "G", and so instances of this class will store one object of "G" type, and have functions to set or get that object. (The reason for this specific example is to provide reason explain that currently a generic class cannot use properties of its restriction type, and so this class has simple get and set methods instead.)
In order to instantiate this class, a type must be chosen, for example the built in string type (in Genie there is no restriction on what type may be used in a generic). The above eample shows one instance of type string and the othe rof type int
As you can see, when the data is retrieved from the wrapper, it is assigned to an identifier with no explicit type. This is possible because Genie knows what sort of objects are in each wrapper instance, and therefore can do this work for you.
The fact that Genie does not create multiple classes out of your generic definition means that you can code a method like accept_object_wrapper above. Since all "TestClass" instances are also Objects, the "accept_object_wrapper" function will happily accept the object it is passed, and treat its wrapped object as though it was a Object instance.
