What this isn't is a complete reference manual for Magenta as it stands now. Apart from a few features that were added here because they were too simple to bother with an explanation in the supplement manual, this is substantially the same as the original document that I cranked out in 1995. If you want to do a full implementation, you need the supplement. However, I wouldn't mind seeing someone write a full manual based on these two documents.
Enjoy.
-Brian (bc99)
The following remain unfinished [and shall remain unfinished, I suppose -- bc99]:
Not to mention Ada's named-block syntax. --bc99
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.
I don't even know if this is possible syntactically. The chief problem with it is that you probably can mix and match constructs, great for the IOMCC (International Obfuscated Magenta Code Contest) but rather useless and even dangerous in practice. -bc99
--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;I have no idea what this example means or where it came from. I have been ridiculed for it in the past and I don't suppose I will ever figure out why I wrote it.
I *think* that the idea is that the zookeeper is told to feed a specific bear; the exception call (which should have been play/punt instead of try/throw) is supposed to trigger a response in case the bear gets a little too much of the munchies. Your guess is as good as mine what the return statement is supposed to do, but I suspect fed should have been declared as a package-level variable. In addition, I have not a clue what those enums are doing in there; bear and food datatypes should be perfectly acceptable. The code *probably* should look like this:
--declare a static or persistent variable somewhere else in the package... bool fed; function feedthebear (bear whichone, food bearfood) returns bool is: whichone:call(); try { whichone:eat(bearfood); throw (whichone:eat(armp)); //armp is a global indicating } //zookeeper's arm return (fed=TRUE); end feedthebear;In this case,
fed
is set within the return statement and the value returned is actually a boolean indicating that the assignment was successful. Of course, the function declaration syntax is a little vague... --bc99Begin/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.
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"); };
<specifier> <object> is: ... end <object>; This construct is most useful for record or class definitions, although it also has other uses [and will henceforth be known as the named-block syntax. Several times throughout this text and its companions you'll find the words "syntactic saccharin", something that's cute but useless. This is the first example.
structure cube is: int x; int y; int z; end;This most likely should be struct instead of structure, but I was trying to be different. --bc99 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 is: print("whaddya, stupid?"); end; case FALSE is: print("whew, that was close."); end; default is: exit; end; }A reminder: this is not a C switch construct. The Magenta construct (see below) has some rather unique properties that make this example rather strange. Also, it indicates that as long as the compiler can tell the difference, the block's name doesn't have to show up in the end statement. --bc99
The general case starts with : and ends with end;. The keyword def is generally meaningless, but is suggested for readability.
Def? would that be default? I have no idea if this meant something.
One other thing to say about named blocks: regarding the previously mentioned named for loop, its syntax is left as an exercise to the reader.
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.
The funny thing about that last bit is that I actually use it in the supplementary manual. I'm not proud. --bc99
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.--And sheer copycatness. --bc99
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?
What, this old thing? I'm not quite sure whether any of this is a good idea. It's interesting, but fairly byzantine, and I'm not sure it's implementable in any practical manner. --bc99
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.
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 daemonThough several of these might have different forms on a Unicode machine, particularly the theta words. The amazing thing is that Magenta has well over eighty (well, maybe ninety now if you consider the supplementary material) reserved words. Of course, according to my copy of the draft C++ standard, there's about seventy there, so this is in good company. --bc99
Operators are predefined with regard to identity and precedence, but meaning can be overloaded. This table is obsolete; I've javafied or C-ified most of the operators.. See the supplementary manual.-bc99
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 [Intercal has a few interesting features. Frightening, but interesting. See the companion doc for info. --bc99] ^ 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)
Simple types: |<storage class>| |<qualifier>| <type> <identifier> |with <unit>|; compound types: |<storage class>| |<qualifier>| <type> <identifier> { <declaration> } |<alias>|;The alias is designed to be used in conjunction with the typedef qualifier.
|...| refers to an implicit identification
|local|--may be declared at any level; will remain in existence until its declaration block goes out of scope
Syntactic saccharin, like auto in C.
global--the variable created has unlimited scope after its creation; implicit at top level
Actually, if I had written down my ideas for packages, this would probably not be in here. It's a rather silly idea anyway.--bc99
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
This one could make for some very interesting implementation issues in the runtime library. On the upside, it would drastically simplify anything involving file-handling, but I never added syntax to define a specific persistent file. See the supplementary manual if you want one possibility. --bc99
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 | for <id> |--accessible to a specified signal block; otherwise local
More specifically, it's intended as a simple way to do interprocess messaging. If a variable is declared as a reciever, outside threads can see it and change it (as long as it's not declared private or something like that). --bc99
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.
As a matter of fact this is completely useless. Does anyone still remember the name of James "Kibo" Parry? --bc99
const--may not be assigned to or otherwise changed
volatile--may change
evil--will seriously f--- up your system if you mess with it
Used in system includes to mark things you shouldn't screw with, I suppose. --bc99
short
long
wide
double--type- and compiler-specific
unsigned--positive only
negative--negative only
wrapped |[<range>]|--rolls over when the value in the <range> parameter is reached; defaults to MAXINT or the maximum value of the enumeration fof integral and enumerated types respectively.
Does Perl do this already? Perl does so much it's hard to tell. -bc99
Note that any data type not explicitly listed is undefined and can thus do whatever the compiler writer's twisted little mind desires.
Enumerations are declared using the following syntax:
|<qualifier>| enum { <label list> } <identifier>;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.
See the section on the Magenta Object Model for full details.
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 language has a static object model based largely on that of C++. The various supported features include:
There are three types of object recognized by Magenta; the distinction is made by the nature of the declaration.
If I had it all to do again, I'd go the Smalltalk/ObjC/Java route and make it an OODL instead of following the C++/Simula static OO design as many on afc suggested. But I hadn't taken Programming Languages at that point either. --bc99
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
The <identifier> is:/end <identifier>
syntax will be used for these definitions.
--This is a skeleton for a declaration for a standard class. class <identifier> is: public { public declarations}; |private { private declarations };| |protected { protected declarations };| |friend { friend declarations }|; end <identifier>;Inheritance is indicated using the following syntax:
public from <identifier>;where <identifier> 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.
A better way would be
class <identifier> extending <identifier> is:
<yada yada yada>
end <identifier>;
But that would make too much sense, and would therefore be inappropriate to this situation :-) --bc99
--this is a declaration for a container class container class <identifier> (<type parameters>) is: with <typeidentifier> as { list of types }; //an insidious plot to force type correctness public { public declaration }; |private { private declaration };| |protected { protected declaration };| |friend { friend declaration };| end <identifier>;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.
I'd like to see someone figure that one out --bc99
--this is a declaration for a module module <identifier> is: public { public declarations }; |private { private definitions };| |friend { friend declarations };| end <identifier>;It is illegal to add a 'from' clause to a module declaration.
Class, structure, union, or module members are accessed using the following syntax:
<classname>:<itemname>Functions, procedures, or tasks can be called using
<classname>:<functionname>( parameter list );Friend items are accessed by their names apart from the class, since they are defined outside the scope of the class.
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 <operator> (<param1>, <param2>) is: { <body> };
Or, everything other than declarations. I got very sloppy writing this manual, and the section headings have gotten quite confusing doing this markup. --bc99
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...
If some had had their way, Magenta would have been a functional language, and maybe only if and switch would have made it in here. But that's not how it worked out. With the exception of the non-block-oriented stuff, this has been superseded by what's in the supplementary manual anyway. --bc99
The {} syntax will be used in the following examples.
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 { 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 (<initial condition>; <exit condition>; step <quantity> |except <condition>|) { 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. I have much to say about this construct in the supplementary manual. Basically, cute but stupid.
[We forgot
not pointed out: labels are literal addresses. That would make alter and entry constructs possible. See the supplement for details. Also, the supplement details a comefrom structure just for the halibut. --bc99
Disparaged? Why? It's in here for the same reason as setjmp/longjmp in C: to unwind the stack if things get messy. It may be disparaged, but it shouldn't be.
No it isn't. The individual cases are blocks, not simply labels, so there is no fallthrough. See the supplementary manual. --bc99
If Magenta has a strong point, it's the IPC handling. It's very amateurishly designed, but what's there is quite useful. By treating when and during as control structures, the process of writing multithreaded controls is much simpler. Of course, it would be a pain to implement, but Magenta, at its heart, is not a simple tool, but a fuzzy-minded collection of things that seemed like a good idea at the time. --bc99
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.
Punt makes as much sense as throw, if you think about it, and more than raise. The syntax was nevertheless quite awkward, and a bit silly. -bc99
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.
I promised an explanation of what I was smoking; unfortunately, I think like this as a general rule, and smoking something would probably make me sane. lest is possibly one of the weirdest constructs in the language, and it is very much Intercal-inspired. The semantics are something like on error goto in Basic, though good taste might limit it strictly to sanity checks and that sort of thing. Interestingly, I believe it predates the finally clause in Java, and seems to do exactly the opposite. Needless to say, if I was creating Magenta now I'd have both of them in there. --bc99
{} block syntax will be used for the following descriptions.
Canadian rules, eh? Something to do with the fact that as a BC student (now grad), Doug Flutie had to be a hero of mine. To those of you living under a rock, before ripping it up in Buffalo Flutie was the single best damn quarterback the Canadian Football League had ever seen. I think it was a tribute. --bc99
This meant something at the time. I swear. As I said before, lest is in here because it sounded good. I believe the idea was that a lest clause would be executed if the block it was attached to failed for some reason. Another way of doing an on error goto, I suppose. --bc99
Because any language that comes down from on high has to have proper thread support. None of that silly library business. --bc99
The concurrency model of the Magenta programming language is based on a
three-dimensional grid. Each axis represents an aspect of the current
process:
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
Given a process proc, the y and z axes are defined as
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 declared a daemon task; these become active when declared and are terminated when they go out of scope.
At the time I had never heard of Befunge; this mess was loosely inspired by the original Orthagonal. The basic point here was that I couldn't see any real use for multidimensional process control *unless* it had something to do with concurrency. I know better now; it has no use at all :-) --bc99
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:
Wow. What little I knew. What's missing here? You take your pick. Sockets. Semaphores. Shared Memory. Synchronization. Lots of other IPC-related junk that probably begins with S. Doing it over again I'd squish it all into the runtime library and build an object-POSIX type interface using streams and SysV IPC together. But I never did get to the runtime library. --bc99
Not too well thought through, this part. It would work, but I'd probably do it somewhat differently if I was redesigning it. --bc99
Magenta could have gone in half a dozen different directions. I was looking for something of a systems-programming language with pretensions of dynamic object-orientation, like ANSI C++. A lot of the alt.folklore.computers crowd (henceforth known as "AFCers") were pushing for features more in common with Lisp or ML, such as a functional paradigm; one suggestion even took it to an extreme by suggesting that the source code be kept in its own object so that a program could be quit simply by a call to the effect of
I have a few gripes with the language as it stands anyway. My liberal use of brackets almost completely fails to make sense four years later, and (as you read in my note on the bearfood program) I have no idea how a function call was supposed to work. The object syntax came out slightly Smalltalkish, interesting given the language's rather static nature. The libraries were undefined, although I had some inkling that a basic graphical interface was part of the package. Filesystem? No idea. Networking? Uh, no. Console? Well, more or less C-like, though extended a bit.
And then there's the manual itself. It would be an understatement to say it was sloppy; I gave up early on the sections, resulting in a poorly-organized mess. The HTML markup for the annotated version has been an absolute bitch, but it resulted in a much more usable document, I think. As a result, it now has proper section references, plus a few things that are here now that were left out before. This may be enough to help make sense of the whole mess, in case someone was crazy enough to try to implement this. There are many changes to the old document, but the reorg is probably the most important as far as readability is concerned.
There's a separate HTML doc on this site that describes what could have been part of Magenta, including the package system (a deranged parody of Java's), variagents (sort of object-method-as-procedural-thread; the name came out of a travesty generator), and a few things I'd throw in now, including fragments that were considered for the Dominatrices. Check that out for even more weirdness, and feel free to add things.
/Brian
until <condition> { <loop body };
5.1.1.5 do/until
do { <loop body> > until ( <condition> );
Don't know why. --bc99]5.1.2 Block control
5.1.2.1 Goto
goto <label>;
goto is just an unconditional branch. It does not work between
functions.5.1.2.2 set/jump
set <label>;
jump <label>;
set sets up a position in the program for jump to jump to. It works between functions but is disparaged.5.1.2.3 break
break;
Jumps out of the current block to the next level out. 5.1.2.4 continue
continue;
Jumps out of the current iteration of a loop and begins the loop again.5.1.3 Conditionals
5.1.3.1 if/else
if (<condition>) { <statements_if_true> } |else {<statements_if_false>}|;
Basic if statement with optional else clause.5.1.3.2 Switch
switch <condition_variable> begin
case (<condition>) { <statements> };
...
default { <statements> };
end;
Basic switch statement.5.1.3.3 unless
unless <condition> { <statements> };
Meaning don't even bother if it ain't true.5.1.4 IPC Structures
5.1.4.1 when
when <task condition> { <statements> };
Stops current thread to watch for a condition in another thread, then
executes its body.5.1.4.2 during
during <task condition> { <loop body> };
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.5.2 Exception handling
5.2.1 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. 5.2.2 Syntax
5.2.2.1 play/punt
play { questionable_routine } punt |to| <catchname>;
This is the syntax for a play/punt block. The 'to' is optional. [Why? Syntactic saccharine. It's becoming my favorite phrase.--bc99] 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. 5.2.2.2 catch
catch <catchname> { cleanup_routine } |<return_to_label>|;
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.5.2.2.3 lest
<loop_type/setup> { body of loop } lest ( condition ) |<catchname>|;
lest (condition) { routine to run } |<catchname>|;
The second is useful for failsafing non-loop constructs.5.3 Concurrency
5.3.1 Concurrency model in the Magenta programming language
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.yp=#lastproc+1
.
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.
<taskname> [<number/priority coordinates>] ( <parameterlist> );
A task may be defined in terms of priority.5.3.2 The signal/reciever keywords and intertask communications
5.3.2.1 Signal
signal <task[:label]> [when <condition>] {items to send} to <reciever>
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.
5.3.2.2 reciever
reciever <type> <variable name>
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. 5.4 Memory Management
5.4.1 alloc
alloc (<type>);
Built-in function that returns a pointer of the specified type to the caller.5.4.2 kill
kill (<type> |, <offset>|);
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).5.4.3 gc
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.--End of the original Magenta reference manual--
Parting Comments, 1999 update
Source:kill()
.
24 September 1999
Click here to return to the Magenta page.
Click here to return to the Turing Tarpit.