Mango has a rich set of primitive types. This includes logicals (2 state binary [on/off], 3 state boolean [true/false/unknown], or 4 state signals [1/0/x/z]), characters (ascii, utf, etc), base 2 registers (user specified size in bits), signal buses (each signal can have 4 states [1/0/x/z]), cardinal numbers (unsigned integers from 1 bit to 64 bits), integer numbers (signed integers from 8 bits to 64 bits), subranges (upper and lower bounds are user specified), rational numbers (a ratio of 2 integers), fixed point decimal numbers, floating point numbers, and unlimited precision integers.
In addition, mango allows control over how overflow is handled (throw an exception, limit the result, or wrap around to the begining), byte order can be specified within the type system (numbers can be marked as have network or host byte order, with conversions happening automatically), and cardinal and integer types have base 2 fixed point fractional precision.
Some primitive declarations:type boolean of logical/boolean; ** a fixed point unsigned integer with 4 bits of fractional precision type fixed_point_cardinal of cardinal/32 {4} having overflow checked; type float_32 of number/32; type dollar of decimal {10:2}; type reg_16 of register {15:0}; type upper_32 of signal {63:32}; type ascii of char/ascii having overflow checked;
Mango has built in support for complex numbers and numeric units. Both are specified within the type system. Having complex numbers built into language allows literals to be specified in the real or complex plane, as well as giving the compiler a better chance at optimization. Having numeric units built into the type system increases the degree of static analysis that can be done upfront, lowering the incident of bugs. For example, declaring a floating point complex number and initializing it is accomplished as follows:
declare x of number/32 complex; assign 5 + 5i into x;A numeric units example:
declare time of integer unit seconds; length of integer unit meters; acceleration of integer unit seconds per meters^2; assign 30 into time; 100 into length; ** this assignment works assign time / length / length into acceleration; ** this doesn't assign time * length into acceleration;
Mango has built in coordinate and matrix types. This is adventageous because having them as part of the type system offers seamless integration and upfront static analysis, as well as holding out the potential for the better optimizations.
Some examples:
type point of integer/32 having overflow limited coordinate {x,y,z}; type matrix_2_2 of integer/32 matrix {2:2}; program test is declare p of point; assign [1,1,1] into p; incr p with [2,2,2]; declare m of matrix_2_2; assign [[1,4], [2,3]] into m; magnify m by 5; ** multiplies each matrix element by 5
Mango has anonymous type products, or tuples. This offers convenience when simple pairs or triplets are needed. There is no need to declare a named structure. Also, tuples allow for multiple return values from functions. Some examples:
** a fixed string of length 64, composed of a integer-boolean pair type string of [integer, boolean](64); ** A function type type fn of [left: integer, right: integer] -> [integer, boolean];
Mango allows the programmer to define thier own literals. In addition, mango provides literal expressions, ie expressions that are evaluated at compile time. This interpreted language allows for the creation of complex literals that are much easier to write and understand. In addition, combined with options and parameters, it allows for dynamic configuration of the program. Mango also provides literal macro's the enhance expressiveness.
literal page_width is 80; literal seperator is (' ', page_width); ** creates a string of 80 spaces literal name is "joebob briggs"; macro pick with a, b, c is min(a,b) + max(b,c); literal test is pick(10,30,50); literal file_seperator is #operating_system such_that "linux_x86" yields '/', "windows_95" yields '\\', "solaris" yields '/', "windows_xp" yields '/', else '?'; literal version_string is *major_version & "." & *minor_version & "." & *program_revision;
Mango provides object orientation. Unlike traditional approaches, however, mango differs in two important respects. First, like language such as Java or C#, it uses interfaces to achieve polymorphism. Second, it uses aggregation rather than hierachical inheritence to achieve code reuse.
The combination of interfaces and aggregates allows the programmer to avoid the problems that plague traditional object oriented implementations. First, the use of interfaces allows the programmer to avoid tie-ins to specific implementations. The programmers does not need to inherit any code to establish a type relation. Seperating type relations from code reuse free's the programmer from having to worry about functionality changes in superclasses in cases where the superclasses implementation is not used. Second, the use of aggregates provides a much cleaner and simpler means of code reuse. In this style, all classes that compose the aggregate are all named up front. There is no hierachy that needs to be searched to understand class behavior. Moreover, aggregation provides the benefits of multiple inheritence without all the complexity surrounding naming conflicts that plagues a hierachical implementaion. Finally, the use of aggregates and interfaces is semantically cleaner. Having hierachical type relations or inhertance trees forces the programmer to model thier enviroment in a hierachical way, even when such classifications are not applicable.
Mango also differs from other languages in how interfaces are related to classes. Instead of specifing that relationship as part of a classes definition, interface mappings are provided external to a class through a special link directive. Having these mappings made externally gives the programmer added flexibility as they can map interfaces to classes without redefining the class. Check out this link for an excellent explaination of the evils of implementation inheritence.
Some simple examples:
aspct asp_1 is method xyz with integer is ...; method abc with integer is ...; aspct asp_2 is method def with integer is ...; method abc with integer is ...; interface intfc is method abc with integer; ** ** Aggregate uses method abc from asp_2. ** aggregate aggr is insert asp_1; insert asp_2; link aggr to intfc;
Mango provides getter and setter methods. This has proven to be syntactically convenient.
object test_obj is state ppp of integer; ttt of cardinal; property ttt of cardinal is query return max(.ppp, .ttt); update assign operand into .ttt when operand > 0; ** automatically creates simple query and update methods for variable 'ppp'. property ppp; program test is get obj of test_obj; assign 10 into obj.ttt; assign 20 into obj.ppp; print obj.ttt, eol;
Mango utilizes manual memory management. Unlike most manual management schemes, it is completely safe. That is, it is not possible (unless one circumvents the type system) to have dangling pointers. To accomplish this, Mango uses a three pronged approach: type durations, sentries, and recycling.
Firstly, all types can have one of three durations: static, local, or arbitrary. Static types live on the heap and are never destroyed. Once created they exist for the duration of the program. Local types exist on the stack. They are only valid for the duration of their respective scope. Arbitrary tpes live on the heap, but can be destroyed. By specifying the duration of a type, the compiler can statically or dynamically check to ensure that no dangling pointer are created.
In mango, local types are checked statically. That is, any assignment is check so that a reference to a locally defined type cannot level it's scope. Static types would require no check, as they exist forever. Arbitrary types require a dynamic check to ensure they still exist.
For arbitrary types, the dynamic check is accomplished through the use of sentries. A sentry is a small data structure that accompanies the creation of an arbitrary type. Whereas an arbitrary type may be deallocated on the heap, it's a associated sentry exists for the duration of the program, and indicates if the type is still allocated. So for any read/write access of an arbitrary type, it's sentry must be checked to see if that type is still valid. To make this look up quick, all pointers to arbitrary types are fat. That is, the contain an address to the data, as well as an address to the accompianing sentry.
The problem such a scheme presents, of course, is a massive proliferation of sentries. To avoid this, Mango uses an arena allocation scheme. A group of arbitrary types all use the same arena for allocation. While new datums can be allocated arbitrarily, all items within an arena can only be dellocated as a group. Since deallocation is shared, only one sentry is needed for all objects in the arena. Having only one sentry per arena prevents thier proliferation. The use of arenas combined with the on chip caches of modern processors will mean that sentry lookups will have minimal cost.
In addition to arenas and sentries, mango provides an additional means of managing the heap space in safe way. This method is recycling. Recycling is used for statically allocated datums. When statically allocated objects are reclaimed, they are not deleted from the heap. Rather, they are simply reset to an intitial state and left on the heap. When the user requests a static object, the allocator looks for any returned datums of the same type, and "recycles" them. Since recycled objects never go out existence, there is no chance of dangling pointer problems.
procedure test_arb of a of integer' is print a, eol; procedure test_loc of a of (integer)' is print a, eol; procedure test_it of b of (integer)'' is declare x of integer; call test_arb with x; ** The compiler will give a type error call test_loc with x; ** This works assign x into ^a; ** the compiler will complain about passing a local type out of scope program test is new global_arena of system::arena; ** create an arena new y of integer' from global_arena; ** allocate this pointer on the stack print y sentry address, eol; ** print the address of the sentry call test_it with y; get z of integer; ** create a statically allocated integer delete z; get q of integer; ** recycle the integer require z address == q address; ** this should evaluate to true
Each source code file is by default a module. Modules can include directories as part of thier name. so a module with a base name "io" in directory "sys" would have a full name of "sys/io". Each module also is part of a namespace. All symbols within a module belong to that namespace. If no namespace is provided, the default namespace is the base name of the module. So if module "sys/io" has no namespace provided, the default namespace for that module is "io".
While mango has namespaces, naming is not hierachical, but rather geometric. That is, names for symbols occur within a 4 dimensional grid. The dimensions being: namespace, keyword, extension and aspect. When modules are imported, this 4d space is collapsed into one dimension, utilizing the keyword and/or the extension of a symbol. Symbols can be accessed using either the four dimensional proper name, or it's 1 dimensional alias. This collapse from 4d to 1d space is the means by which Mango handles operator and keyword overloading.
Having "shallow" overloading (ie superficial), has the advantage in that it prevents conflicts when symbols are overloaded. Every symbol has a unique name by which it can be referenced.
module test in system; ** test is part of the namespace io procedure test is ** procedure test's full name is system::test ...; enum color is blue, green; --------------------------------- module io; ** located in the directory sys import sys/io; ** module importing itself...this will have no effect import test; procedure test is ** the procedure test full name is io::test ...; ** named io::color@red, io::color@green, io::color@blue enum color is red, green, blue; program doit is call test; ** this will cause a unresolvable symbol name conflict call system::test; ** this will work just fine print red; ** this will print the word 'red' print color@red; ** this will print the word 'red' print blue; ** this will an unresolvable symbol name conflict print io::color@blue; ** this will work
Mango supports incremental compilation. Mango accomplishes this by generating header files automatically, and comparing the syntax of a modified source file with the previously generated header file. If the public aspects of the source have changed, then recompilation will occur. Mango also does dependency analysis. Only the files that need to be recompiled will be recompiled, pending changes in a modules dependencies.
To enhance incremental compilation, Mango include two import directives: include and import. The import directive is private; only the module that is importing can have access to the foreign module. The include directive is public. In this case, the included modules symbols are exported to other modules.
Mango provides a rich set of container types. Having containers withing the language rather than a library is a syntactic convience. There are a variety of operators that work only on collections. In addition, have container types built into the language offers enhanced opportunities for optimization. The container types mango offers include:
entry: a node of a linked list segment: a combination of string and pointer list: a list, with elements allocated in pages, FIFO order stack: a (prioritized) stack sequence: a list, with elements allocated in pages, LIFO order queue: a (prioritized) queue mask: a bit mask range: a numeric range with upper and lower bound set: a hashed set. Also doubles as a one-to-one map table: a hashed one-to-many mapping. Useful for rolling hash tables group: a special collected used for comparisons graph: used for frequency tablesSome examples definitions:
type entry of integer entry; type segment of ascii segment {100}; type list of user_record list {256}; type stack of <user_record, cardinal> stack; type sequence of user_record sequence {32}; type queue of user_record queue; type mask of ascii mask; type range of number range; type set of ascii string set {19}; type table of <ascii string, cardinal> table {89}; type group of integer range group; type graph of <ascii string, number> graph;
Though mango is a procedural language, it does provide constructs that give it a bit of a function feel. One of the constructs is the function. In contrast to procedures, which are a series of statements, functions are essentially a single expression, extended with two case constructs. One of these case's is condition, the other is conversion. For example:
function test with ascii+ string string, integer/32 yielding logical/boolean is s[x] when x > 0, s[y] when y > 0, "x" otherwise of_type ascii+ string such_that "true" yields true, "false yields false else unknown where s = operand.1, x = operand.2, y = x + 100;
In addition to functions, Mango also provides constants. A constant is a function that takes no arguments and is evaluated only once. The result of this evaluation is cached, and any subsequent calls use the cached value. Constants are a clean way of instatiating global objects (aka the once function in eiffel).
constant global_arena of arena is get arena;
One of the intersting features of Mango is it's built in I/O. Mango allows I/O streams to be typed. This provides added safety and convienience. There are six types of I/O devices in mango:
file: an operating system file text: a line oriented text file stream: an I/O stream port: a connection listener database: a database document: a document
Below is a simple example using text I/O. The compiler is automatically able to infer the type of variable "line" because the file handle tx is typed.
open tx of ascii text where path = "test.txt"; foreach line in tx over i do print [width=5] -> i, ":", line; close tx;
enum format_codes is eof, eol; writer print_ascii with [ascii stream, (ascii+) string | format_codes] is ...; program hello_world is write stdout with "hello", " ", "world", "!", eol;The write procedure "print_ascii" is called for each argument.
Since Mango has tuples, supplying format information is very easy:
declare x,y of integer where x = 100, y = 10_000; write stdout with "[", [[width=10, align=right], x], " ", [[width=20, align=left], y], "]", eol;
In addition to write, mango include two other output operators: print and log. Print is similair to write, except that the target stream is standard out. The log operator is identical synatically to write. However, logging is atomic. All arguments are passed at once to the target procedure.
Mango also includes a read operator. The read operator is typed and straightforward:
program echo is declare i of integer; read (i) from stdin; read [a of ascii list] from stdin; print [[width=3], i], ":", a, eol;
Too help ease build customization, Mango provides Parameters and Options. Parameters are variables used by the compiler
when statement
when clauses in type declarations
pause, before, after atomic logging during statement assertion statements