{ ... } or begin ... end keywords. All indentation in Converge must be in the form of spaces; no other form of whitespace is acceptable. Conventionally 4 space characters form one level of indentation; however you may use any number of space characters to indicate indentation provided you are consistent within your own code.
Hello world! program:
import Sys
func main():
Sys::println("Hello world!")
As expected, when this program is run it prints out Hello world!.
Note how the body of the main function is indented from its func main(): definition. The name of this function is also significant. When a program is run, Converge looks for a function called main and executes that function. If no such function exists, an exception is thrown. Sys is a built-in module. To lookup module definitions such as the println function one uses the :: operator.
hello.cv, it can be compiled and run automatically as follows:
$ converge hello.cv Hello world! $In other words, when the VM is passed a source script, it automatically compiles dependencies, links, and executes the file. It also caches the results of the compilation so that subsequent runs are much quicker. It is often instructive to see what steps are being taken prior to the file being run. The
-v switch to the VM shows the auto-make steps in operation:
$ converge -v hello.cv ===> Compiling /tmp/hello.cv... ===> Finished compiling /tmp/hello.cv. ===> Linking. Hello world! $Converge's auto-make facility ensures that all files in a project are upto date; its chief limitation is that it sometimes has to compile individual files multiple times to ensure that this is the case, even when a user may know that this is not strictly necessary. In such cases, it may be more efficient to use the individual compiler tools.
If you encounter strange
compilation problems (perhaps after moving files from one machine to another), converge can be instructed via the -f switch to ignore all your cached bytecode files and perform compilation from scratch, updating all cached bytecode files. Although this is rarely needed, it can be useful on occasion.
func m(ai, ..., an, aj := <default value>j;, ... am := <default value>m, *v)
where i, j >= 0 and v is optional. Put another way, this means that (optionally) normal arguments are (optionally) followed by arguments with default values and (optionally) completed by a final 'var args' variable.
The caller of a function must specify values for all normal arguments. Arguments with default values for which no value is specified by the caller will have their default value evaluated and assigned to that arg. All remaining arguments are put into a list and assigned to v.
For example a printf like function would be specified as follows:
func printf(format, *var_args):
...
An fprintf style function which defaults to using standard out might look as follows:
func fprintf(format, stream := Sys::stdout, *var_args):
...
Default values are evaluated each time a value is not passed to a particular argument. This is substantially different than the mechanism found in Python. Note that Converge has no concept of function overloading based on e.g. function arity.
0 then 1 then 1.
func main():
x := 0
Sys::println(x)
func f2():
Sys::println(x)
x := 1
f2()
Sys::println(x)
Assignment of a variable in a block causes that variable to be made local to the block. The following program will thus print 0 then 0 then 0.
func main():
x := 0
Sys::println(x)
func f2():
x := 1
Sys::println(x)
f2()
Sys::println(x)
In order to assign to a variable in an outer block, the nonlocal statement at the beginning of a function punches a hole up to an appropriate layer. The following program will thus print 0 then 0 then 1.
func main():
x := 0
Sys::println(x)
func f2():
nonlocal x
x := 1
Sys::println(x)
f2()
Sys::println(x)
Note that class elements are deliberately excluded from the standard scoping mechanism so that the following is invalid:
class Dog:
type := "Dog"
func get_type(self):
return type
As the above suggests, the first parameter of a bound function will always be set to the selfobject. Conventionally this parameter is always called
self. Via this variable the current objects slots can be accessed:
class Dog:
type := "Dog"
func get_type(self):
return self.type
lambdas
small functionssuch as a specialised lambda form. However sometimes the indentation syntax of Converge can be inconvenient when passing functions containing only a few expressions as arguments to another function. In such cases one can use the alternative curly bracket syntax as follows:
Functional::map(func (x) { return x.to_str() }, list)
. (dot) operator.
A simple definition of a class is as follows:
class Animal:
func init(self, name):
self.name := name
func get_name(self):
return self.name
func set_name(self, name):
self.name := name
New objects can be created via the new slot of a class:
fido := Animal.new("Fido")
The resulting object can then have its slots accessed and assigned in the standard fashion:
fido.set_name("Dido")
Sys::println(fido.get_name())
fido.name := "Lido"
When an object is first created, its new slot is called to set-up the object; it is common to override the default action provided by new as in the above example.
Functions within a class are slightly different to normal top-level functions; when they are extracted from an object they are bound to that object. The distinguished variable self is automatically assigned to the bound object. In all other respects bound and unbound functions are identical.
class Dog(Animal):
...
If more than one superclass is specified, then any naming conflicts between two classes are resolved in favour of the class specified later in the superclass list.
C.instantiated(o) succeeds if o was created by C's new function. C.conformed_by(o) succeeds if o has a matching slot for every slot defined in C (note that o may have been created by a different class but still conform to C).
For example whilst the following module is correct:
class Animal:
...
class Dog(Animal):
...
reversing the two classes would be incorrect because the creation of Dog would attempt to access the Animal variable before it has had a class assigned to it.
However notice that it is only at the top-level that the ordering of elements is important. For example the following module is perfectly valid because by the time the new_dog function can be called lexical scoping will have ensured that Dog will have been assigned a value:
func new_dog():
return Dog()
class Animal:
...
class Dog(Animal):
...
:: operator to access definitions within a foreign module. It should be noted that modules are also normal objects and that a modules' normal slots can be accessed via the standard . (dot) operator as any other object.
import statement imports an element into a modules namespace. By default the imported element is assigned to a variable which has the name of the final part of the import statement e.g. import A::B::C will bring into existence a variable C and assigns to it the value of the element A::B::C. This behaviour can be altered by using the as X suffix. e.g. import A::B::C as D will bring into existence a variable D and assigns to it the value of the element A::B::C.
Converge needs to be able to determine at compile-time exactly which modules are imported by the import statement. To facilitate this, the variable created by an import statement can not be assigned to. In other words this is illegal:
import A::B::C as D ... D := 4
list := [1, 2, 3]
set := Set{3, 2, 1}
dict := Dict{"a" : 1, "c" : 3, "b" : 2}
str := "123"
list := [1, 2, 3, 4] Sys::println(list[1]) // prints 1 Sys::println(list[0 : 2]) // prints [1, 2] Sys::println(list[-1]) // prints 4 Sys::println(list[1 : -1]) // prints [2, 3] list[3] := 10 Sys::println(list[-2]) // prints 10 list[1 : -1] := [5, 6, 7] Sys::println(list) // prints[1, 5, 6, 7, 4]Indexes start from zero. Negative indexes count n elements from the end of the list.
dict := Dict{"a" : 1, "c" : 3, "b" : 2}
Sys::println(dict["a"]) // prints 1
dict["b"] := 10
Sys::println(dict["b"]) // prints 10
str := "1234" Sys::println(str[1]) // prints "1" Sys::println(str[0 : 2]) // prints "12" Sys::println(str[-1]) // prints "4" Sys::println(str[1 : -1]) // prints "23"
:= operator to differentiate it from the equality operator ==. Assignment returns the value of its right hand side. The following fragment prints 10:
Sys::println(i := 10)Syntactic sugar is available for addition
+=, multiplication *= and division /= e.g.:
i += 2The above is pure syntactic sugar for:
i := i + 2Since assignment is an expression, assignments can be chained. The following fragment assigns
10 to both x and y:
x := y := 10Assignment can be used to
10 then 20:
x, y := [10, 20] Sys::println(x) Sys::println(y)The number of unpacking variables must equal exactly the size of the list being unpacked or a run-time exception will be raised.
Expressions in Converge can succeed or fail. When an expression succeeds it produces a value; when it fails, various outcomes are possible. A simple example of an expression that succeeds is:
x := 5 < 10This is in fact two expressions. Firstly the expression
5 < 10 succeeds and produces the value 10. The assignment of 10 to x succeeds and similarly produces the value 10. In the presence of an if statement the following results in the expected behaviour, printing Correct:
if 5 < 10 then:
Sys::println("Correct")
else:
Sys::println("Should never get here")
If the expression in the if fails then the following fragment similarly produces the expected behaviour:
if 10 < 5 then:
Sys::println("Should never get here.")
else:
Sys::println("Correct.")
Essentially, the comparison 10 < 5 fails, which causes control to branch immediately to the else branch of the if statement.
Generators are generally used with the for statement which will pump a generator for values until it produces no more. The following fragment prints all elements in the list l:
l := [1, 3, 5, 8]
for e := l.iter():
Sys::println(e)
Functions can be made to produce multiple values (rather than simply return'ing a value) with the yield expression. For example the following function will generate all values from m up to but excluding n:
func range(m, n):
while m < n:
yield m
m += 1
fail
The fail statement causes the function to both return and to transmit failure to its caller.
andand
or
& operator can be used as the traditional andoperator and the alternation operator
| as the traditional oroperator.
The following condition succeeds only if both comparisons succeed:
if i < j & i > 100:
...
Similarly the following succeeds if either the first comparison succeeds, if it is not, the second comparison succeeds (note that this evaluation is, as expected, lazy so that if the first comparison succeeds the second is not evaluated):
if i < j | i > 100:
...
if statement is a bound expression, so the failure of the condition does not cause control to escape back beyond the if statement. Significantly, each separate line in a source file is automatically a bound expression, so if the expression on one line fails, control does not backtrack to previous lines.
Alternation is a special kind of generator which successively generates each of its values. For example, the following prints 1 then 2:
for i := 1 | 2:
Sys::println(i)
==, + etc. The infix operators are simply syntactic sugar for calling these functions, and thus one can redefine their meaning by providing different definitions of the infix functions within objects. As well as the standard mathematic binary operators, the is operator can be used to test two objects for identity equality.
if control structure
if <condition> then:
...
elif <condition> then:
...
...
else:
...
Zero or more elif blocks can be specified. A maximum of one else block can be specified.
ndif control structure
ndif <condition> then:
...
elif <condition> then:
...
...
Zero or more elif blocks can be specified.
ndif is the no default if
structure; in other words, a run-time exception is raised if none of the clauses' conditions succeeds. Since the default
action is therefore to raise an exception, ndif statements can not contain an else clause.
for control structurefor evaluates expression and, if it is a generator, pumps it until it fails. If the loop terminates naturally through the exhaustion of expression, the optional exhausted block is executed. If the loop is terminated through a break command, the optional broken block is executed. Both exhausted and broken blocks may optionally be specified on a for structure.
for <expression>:
...
exhausted:
...
broken:
...
while control structurewhile repeatedly evaluates expression until it fails. If the loop terminates naturally through expression failing, the optional exhausted block is executed. If the loop is terminated through a break command, the optional broken block is executed. Both exhausted and broken blocks may be specified on a while structure.
while <expression>:
...
exhausted:
...
broken:
...
Note that while differs from for in that expression is reevaluated on each loop. This means that the following fragment will loop infinitely printing 1:
l := [1, 2, 3, 4]
while i := l.iter():
Sys::println(i)
since the iter generator will be recreated anew on each loop which is probably not what was intended.
break and continuebreak causes the innermost loop to be terminated immediately. continue causes the innermost loop to attempt its next iteration immediately.
It is illegal to use break or continue outside of loops.
try ... catch
try:
...
catch <Exception1> to <v1>:
...
catch to <vn>:
...
One or more catch's must be present. Zero or more catch's catching specific exceptions may be specified. The final catch may optionally not catch a specific exception at which point it will catch all exceptions that occur.
Object is the base class from which all other classes ultimately extend. If a class does not specify a superclass, then Object is automatically made its one and only superclass.
find_slot(name)
|
Returns the value of the slot name if it exists. Fails if name name does not exist.
|
get_slot(name)
|
Returns the value of the slot name. Throws an exception if name does not exist.
|
set_slot(name, value)
|
Sets the value of the slot name to value.
|
Class or one of its subclasses. Thus the classes we have seen earlier in this document have elided the metaclass which defaults to Class:
class Animal metaclass Class:
...
New metaclasses can be defined and used thus:
class Singleton(Class):
func new(self):
if not self.find_slot("instance"):
self.instance := exbi Class.new()
return self.instance
class M metaclass Singleton:
...
Sub-classes of Class generally need to override the new method which is invoked when a class is instantiated, and is expected to return a new object.