The following is the original Magenta manual, now almost exactly five years old as I write this (3/18/2000). It is presented typos and all.
The Magenta Partial Reference Manual
------------------------------------
20 March 1995
Brian Connors
Boston College
This document is (c) 1995 Brian Connors until its completion, at which
point it will pass into the public domain.
The following remain unfinished:
-Function call syntax
-calling conventions
-standard libraries
-binary/logical operators
-enumerations
comments and block structure
----------------------------
note: this language, for convenience, will be referred to as Magenta in
these notes until we can come up with something better.
1.1 block structure delineation
The procedural subset of Magenta derives from a hybridization of the
Algol and C models, with a few ideas to itself. Both an Algol
begin/end and C {} block format are supported.
Magenta is closer to Algol or GNU C than standard ANSI C in the
implementation of blocks in that functions can be local or global.
Although begin/end and {} constructs are interchangeable, they may not
be used together in the same block.
1.1.1 begin/end
Begin/end is the primary form. It is best used for procedure or function definitions:
--this example actually demonstrates both forms, but the prime focus
--is the begin/end clause
function feedthebear (enum[bear] whichone, enum[food] bearfood)
begin
bool fed; //if all goes well...
whichone[call] ();
try {
whichone[eat] (bearfood);
throw (whichone[eat](armp)); //armp is a global indicating
} //zookeeper's arm
return (fed=TRUE);
end;
Begin/end is probably most intuitive when used as shown above, that is
to indicate the beginning and ending of a procedure definition. Note that
a semicolon must appear after the end statement.
1.1.2 braces
The use of braces as in C is interchangeable with begin/end, but finds
its best use in other circumstances, such as loop definitions and the
like where begin/end would be unnecessarily clumsy. Like begin/end, the
clause must end with a semicolon.
--this loop prints out the numbers from 1 to 10 to the console in increments
--of .5.
for i(1 10 step .5) {
print(i,"\n");
};
1.1.3 is:/end
is:
...
end ;
This construct is most useful for record or class definitions, although it
also has other uses.
structure cube is:
int x;
int y;
int z;
end;
This is pretty much syntactic sugar. The construct also extends to
other forms, such as the cases in a switch block:
switch(i){
case TRUE:
print("whaddya, stupid?");
end;
case FALSE:
print("whew, that was close.");
end;
default:
exit;
end;
}
The general case starts with : and ends with end;. The keyword def is
generally meaningless, but is suggested for readability.
1.2 comments
Magenta comments come in three styles, derived respectively from Ada,
C++, and C. The third type is actually not a comment but a form of
conditional compilation directive.
1.2.1 block comments
Block comments are Ada-style, beginning with a double hyphen at the left
margin and terminating with a carriage return. It is highly
recommended that they not be placed in the actual body of the program.
--This is a Magenta block comment. The double hyphen only works to the
--end of the line. It was chosen for readability and unclutteredness.
1.2.2 line comments
Individual comments in a line of code are done with a double-slash a la
C++. They end with a carriage return.
int foo(15,15); //what does the foo map look like?
1.2.3 code comments
In Magenta, the /* */ C-style comment is reserved to act as a
conditional compilation directive. The code within the beginning and
end symbols should be legal code if the conditional compilation
directive placed in brackets after the beginning is used. Otherwise it
is interchangeable with the other two varieties.
/* */ comments may be nested, but only if they are used for conditional
compilation and not as block comments.
/* This is a comment, people. */
/* [?_unix] //the brackets indicate a condition to fill in order to
//compile the following code.
--this, on the other hand, should be legal code.
[else]
--this is the code that gets compiled on another machine.
*/
Definition symbols can be upper or lower case. The following are the
definition-testing symbols:
?condition if the following condition is true...
!condition if the following condition is false...
else assuming the last test was false, do this instead.
Reserved words in the Magenta programming language
--------------------------------------------------
begin end is case
switch if alloc else
for step rerun continue
break while end do
until except wrapped kibo
and or not xor
with exit return call
play punt catch structure
union int char class
wide long double function
procedure member public private
protected friend virtual container
gc goto to lest
task priority It signal
reciever xp yp zp
rp ythetap zthetap persist
volatile const external evil
static persist bool module
from as operator kill
default typedef negative daemon
Operators in the Magenta Language
---------------------------------
Operators are predefined with regard to identity and precedence, but
meaning can be overloaded.
operator default meaning direction
------------------------------------------------------------------
() force precedence
: member l->r
~ external label l->r
-(unary) negative l->r
!(unary) not l->r
-> pointer to (address) r->l
<- contents of r->l
sizeof size of an object r->l
++(unary) increment operand->operator
--(unary) decrement operand->operator
and bitwise AND l->r
or bitwise OR l->r
xor bitwise XOR l->r
~ binary select l->r
$ binary mingle l->r
^ exponentiation l->r
* multiplication l->r
/ division l->r
mod modulo l->r
+ addition l->r
-(binary) subtraction l->r
<< shift left l->r
>> shift right l->r
< less than l->r
<= less than/equal to l->r
> greater than l->r
>= greater than/equal to l->r
== comparison l->r
<> not equal to l->r
& logical and l->r
:= assignment r->l
, comma l->r
Character sets that support them may substitute the appropriate
symbols for ->, <-, >=, <=, $ (==US cent sign)
Declarations
------------
Simple types: || || |with |;
compound types: || || { }
||;
The alias is designed to be used in conjunction with the typedef qualifier.
Scoping qualifiers
------------------
|...| refers to an implicit identification
|local|--may be declared at any level; will remain in existence until
its declaration block goes out of scope
global--the variable created has unlimited scope after its creation;
implicit at top level
static--scope is limited to the block it's created in, but its value
remains constant until creating block comes back into scope
persist--scope is limited to the block it's created in, but its value
remains constant between invocations of the program
private--accessible only within a module or class
protected--accessible within a class or all derived classes (illegal and redundant for modules)
public--accessible to all parts of the class or module's scope
reciever--accessible to a specified signal block; otherwise local
external--defined elsewhere
kibo--value is local, name is global; thus can have different values depending
on scope of the variable. The value of a kibo variable can be defined globally,
but if reassigned locally it takes on a different value local to that scope,
while retaining its global value elsewhere.
Type Qualifiers
---------------
const--may not be assigned to or otherwise changed
volatile--may change
evil--will seriously f--- up your system if you mess with it
short
long
wide
double--type- and compiler-specific
unsigned--positive only
negative--negative only
wrapped |[]|--rolls over when the value in the parameter
is reached; defaults to MAXINT or the maximum value of the enumeration
fof integral and enumerated types respectively.
Data types
----------
Note that any data type not explicitly listed is undefined and can thus
do whatever the compiler writer's twisted little mind desires.
char--character data
char--8-bit ISO Latin-xx characters
short char--ISO-646 7-bit chars
wide char--Unicode 16-bit char
float--floating point numerals
float--standard machine float
double float--double-precision float
int--integer numerals
int--at least 16 bits
short int--at least 16 bits
long int--at least 32 bits
wide int--maximum machine size
bignum--library-defined unlimited integral value
Note: Integral types may be assigned a unit (kg, cm, smoot, etc.).
ptr--pointer--machine-dependent
cstring--library-defined C string (indeterminate-length array terminated
with an ASCII NUL (\h0))
wide cstring--identical to mstring
pstring[]--library-defined Pascal string--may not exceed 255
characters
mstring--native string--identical to C string except an mstring
defaults to 16-bit chars
bool--boolean data--the low-order bit is significant
|array|--contains everything--defined with a bracketed subscript
enum--set of values assigned to specific labels (see section on
Enumerations)
Enumerations
------------
Enumerations are declared using the following syntax:
|| enum { } ;
An enumeration may not have more entries than MAXINT. An enumeration
may be used in any manner identical to an integral type, although it
may not accept a unit.
An enumeration declared 'kibo' may have different values in different
parts of the program, according to the specification of the 'kibo' qualifier.
Composite Types
---------------
See the section on the Magenta Object Model for full details.
structure
-contains any type, including other structures
-contains individual variables of specified types
-does not have any connected member functions
union
-contains any type
-may not be declared const
-sized to hold the largest member
-does not have any connected member functions
module
-contains any type
-allows connected member functions
-allows friend functions
-may not be inherited from or to
class
-contains any type
-allows connected member functions
-allows friend functions
-allows virtual functions
-may be inherited from--multiple inheritance is legal
It
-strictly local
-refers to whatever was previously used in that statement
Use of It
---------
It is an internal data type that is replaced with the local variable
last used in a previous expression within the same block. It, due to
the scoping rules of the language, works only within one level of one
block at a time. It may be used in multiple processes, but the meaning
is strictly local to each one. It is an error to declare It static. It
is also an error to assign to It, since It is implicitly assigned.
task feed_fish(food Fishfood, fish Goldfish) [yp=lastproc+1, \ zp=MINPRIORITY] begin
bowl Fishbowl;
commode toilet;
get(Fishfood);
feed(It, Goldfish); //It=Fishfood here
toilet=Fishbowl:empty();
toilet:flush();
clean(It); //It=toilet here
end;
The Magenta Object Model
------------------------
The Magenta language has a static object model based largely on that of C++.
The various supported features include:
-single inheritance
-multiple inheritance
-container classes
-member functions
-virtual functions
-friend functions
-container classes
-persistent objects
-classes/modules separation
-garbage collection
-public/private access
-protected access (classes only)
-operator overloading
There are three types of object recognized by Magenta; the distinction
is made by the nature of the declaration.
-flat objects--defined based on a structure, a union, an array, or a
primitive data type. The identification as an object is made primarily
for convenience.
-closed objects--defined based on a module
-open objects--defined based on a class.
The module/class distinction is as follows:
-A module is a subset of a class. The declaration may include any type
(including a class or another module as a member) but does not undergo
inheritance. There are thus member functions, but no virtual functions
or container modules.
-A class is a superset of a module. The declaration may include any
type (including a module or another class as a member), and both
virtual functions and container classes are allowed.
Both are subject to garbage collection. Automatic garbage collection is
not built in to the language, but is included as a daemon task in one
of the standard libraries.
Definitions
-----------
class--see above
composition--the insertion of a class or module as a member of a class or module
container class--a class defined in terms of an incomplete definition
with the incomplete part being an indeterminate type to be defined as a
parameter to the class.
friend--affects internals of the class or module, but is defined elsewhere
inheritance--the creation of a derived class by adding on to a base class
module--see above
Object Model statements and syntax
----------------------------------
The is:/end syntax will be used for these definitions.
1. declarations
--This is a skeleton for a declaration for a standard class.
class is:
public { public declarations};
|private { private declarations };|
|protected { protected declarations };|
|friend { friend declarations }|;
end ;
Inheritance is indicated using the following syntax:
public from ;
where indicates the class inherited from. Multiple
inheritance is indicated by multiple 'from' clauses; composition is
indicated simply by including the class(es) to be added in with the
standard declarations.
--this is a declaration for a container class
container class () is:
with as { list of types };
//an insidious plot to force type correctness
public { public declaration };
|private { private declaration };|
|protected { protected declaration };|
|friend { friend declaration };|
end ;
The 'with' clause is required for a container class. The with clause
keeps track of what a virtual function can take; if type correctness is
to be overridden, there is a special 'anything' declaration that tells
the compiler to figure it out for itself.
--this is a declaration for a module
module is:
public { public declarations };
|private { private definitions };|
|friend { friend declarations };|
end ;
It is illegal to add a 'from' clause to a module declaration.
2. Member access
Class, structure, union, or module members are accessed using the
following syntax:
:
Functions, procedures, or tasks can be called using
:( parameter list );
Friend items are accessed by their names apart from the class, since
they are defined outside the scope of the class.
3. Operator overloading
In order to do something predictable to a class or module when you use an
operator on it, there is a built-in operator overloading feature.
To change the meaning of an operator, one must define the operator as a
friend function related to the class or module in question. The
definition may be local.
Syntax:
friend operator (, ) is: {
};
Control Flow
------------
Control flow is of course vital to a computer program. The Magenta
language has a generous set of control constructs, each fairly useful in
their own twisted way...
Looping
-------
The {} syntax will be used in the following examples.
while:
while (condition) { body of loop };
The while loop is evaluated zero or more times, with the exit condition
evaluated at the top of the loop.
do/while:
do { body of loop } while (condition);
The do loop is evaluated one or more times, with the exit condition
evaluated at the bottom of the loop.
for:
for (; ; step |except
|) { body of loop };
The for loop is evaluated according to the 'step/except' clause. The
step keyword defines the amount and direction to increment the loop,
while the except skips the points where the expression indicates.
The index variable may be either an integral type or an enumeration and
may be wraparound types.
during:
during { };
Watches another task within the same program and keeps executing the
loop while that task is true. The loop will always finish its current
iteration when the exit condition is fulfilled.
Block control
-------------
goto:
goto ;
goto is just an unconditional branch. It does not work between
functions.
set/jump:
set ;
jump ;
'set' sets up a position in the program for 'jump' to jump to. It works
between functions but is disparaged.
break:
break;
Jumps out of the current block to the next level out.
continue:
continue;
Jumps out of the current iteration of a loop and begins the loop again.
Conditionals
------------
if/else:
if () { } |else {}|;
Basic if statement with optional else clause.
switch begin
case () { };
...
default { };
end;
Basic switch statement.
unless:
unless { };
Meaning don't even bother if it ain't true.
when:
when { };
Stops current thread to watch for a condition in another thread, then
executes its body.
Exception handling
------------------
Magenta, being as it is such a useful language, has a built-in exception
handling model. The general model is based on the philosophy of passing
the buck--if you can't deal with it, punt.
Well, don't literally punt... Well, okay.
The exception model
-------------------
The 'catch' block is ideally implemented as a separate task under the
main program begun at the point of the punt; failing that a function
block is a legitimate replacement.
The exception frame is set up with a 'play' block terminated with a
'punt' condition. If the 'play' block fails, then the 'punt' condition
automatically transfers control to the specified 'catch' block. The
current task is terminated.
'Catch' blocks return to the original calling function by default; they
can be reassigned anywhere.
The 'lest' clause calls an abort function unless a specific 'catch'
block is specified; it is invoked primarily as an escape when the
normal flow of a loop block is disrupted and its primary use is for
dealing with timing failures involving reciever variables.
Syntax
------
{} block syntax will be used for the following descriptions.
play/punt:
play { questionable_routine } punt |to| ;
This is the syntax for a play/punt block. The 'to' is optional. Under
Canadian rules there is no touchback for a dropped punt; therefore if
it is assigned to a null or illegal handler, the compiler should return
an error.
catch:
catch { cleanup_routine } ||;
This is the syntax for a catch block. The return_to_label is wherever
the return from the catch block is to be sent; as stated above the
default is the routine that called the punting routine.
lest:
{ body of loop } lest ( condition ) ||;
lest (condition) { routine to run } ||;
The second is useful for failsafing non-loop constructs.
Concurrency model in the Magenta programming language
-----------------------------------------------------
The concurrency model of the Magenta programming language is based on a
three-dimensional grid. Each axis represents an aspect of the current
process:
z<-process priority (decreases away from origin)
|
|
|
|
|
+----------x<-intraprocess control flow
\
\
\
\
\
y<-process number
The y-axis can be replaced with a ytheta value for queued processes
with limited lifespans.
The z-axis can be replaced with a ztheta value of user-defined size
for processes with rotating priorities.
Tasks created without explicit positioning are given the values zp=1
(highest priority for a non-main process) and yp=#lastproc+1.
Given a process proc, the y and z axes are defined as
yp=#(task)proc //the process's task number
zp=#(priority)proc //the current priority
If the radial system is used, the values rp, ythetap, and zthetap
(canonically represented using the Greek letter theta as opposed
to the word theta) replace xp, yp, and zp as needed. The theta
values are given a range and the tasks assigned to them are queued
in such a way that as a task passes out of the queue, its number is
reassigned to the next task into the queue.
A zero priority coordinate results in the termination of the calling
thread; the default is a priority of 1.
A zero task number on a linear task axis, if assigned to anything other
than the main thread, produces undefined results. On a radial axis, the
result is a program with no real main thread.
A task is invoked in the same manner as a procedure or function; the
current process continues unless an explicit zero-priority directive is
issued.
[] ( );
A task may be defined in terms of priority.
A task may be declared a 'daemon' task; these become active when declared
and are terminated when they go out of scope.
The signal/reciever keywords and intertask communications
--------------------------------------------------------
signal [when ] {items to send} to
signal passes information between tasks to a specific point within
the recieving task. That point is specified by a label or a
specified condition within the recieving task.
A signal with no specified condition assumes the signal goes into
effect at the first invocation of a reciever variable.
The presence of a label in the signal statement indicates that the
signalled information will go into effect at the corresponding label
in the recieving task.
The presence of a when condition in the signal statement indicates
that the signalled information will go into effect at the first
invocation of the corresponding reciever variable after the
when condition is fulfilled.
comments
-It is an error to not specify a task.
-It is an error to send an empty sender field.
-The effects of not specifying a reciever are undefined.
-It is an error to signal to a variable not declared
a reciever variable.
reciever
reciever is a type qualifier that indicates that the variable specified
may be used as a container for a signal from a different task. Any
local variable which can be changed from outside the task must be
declared a reciever variable.
Memory Management
-----------------
alloc ();
Built-in function that returns a pointer of the specified type to the caller.
kill ( |, |);
Built-in function that destroys the allocated object (if offset is specified,
the result is that the object occupying the specified area after the object
having the kill called on it is killed instead).
gc;
Sweeps up all unused non-persistent objects and variables and resets all
inactive kibo-declared variables to the value of the widest-scoped declaration.
Click here to return to the Magenta page.