Prism Mechanics, Part 3: Reflection Metamodel

Jim Little jiml@inconnect.com
Thu, 07 Jan 1999 22:06:04 -0700


Before I get into the meat of this essay, let me ask a question: Where
should I put these Prism posts?  Now that the tunes-spec list is online,
should I be posting there?  My gut feeling is 'no,' but I'd like your
opinions, and your reasons, too.  :)  Thanks!

This is the last Prism Mechanics essay.  I'll be posting a conclusion
(but probably not for a couple of days), but this is pretty much it. 
With this essay, I've completely described Prism as best as I'm able. 
What happens next is up to you.  I can continue to discuss the details
and ramifications of Prism in response to your questions and comments,
or I can quietly fade back to lurking.  

Either way, if you see a flaw, I hope you'll tell me about it.  Billy
Tanksley's critique of Prism's meta-metamodel resulted in a drastic
improvement to it (in my opinion), and I hope the rest of you will be as
forthcoming with your opinions.

Anyway, on to the article:


Prism Mechanics, Part 3:
Mirror, Mirror, On The Wall

This essay describes Prism's Reflection metamodel, the "compiler" part
of Prism.  If you'll recall, Prism consists of three parts -- a semantic
meta-metamodel, which I described in part 2 of this series, and a global
heap, which I'll briefly describe below.

A passable understanding of the Prism meta-metamodel is crucial in order
to understand this essay.  That's because the Reflection metamodel
manipulates the meta-metamodel (in a way).  In addition, the Reflection
metamodel is DEFINED in the meta-metamodel.  :)  If you're not entirely
clear on how the meta-metamodel works, and you want to understand this
part of Prism, I recommend re-reading Part 2 of this series as well as
the "Prism Glossary" message I recently posted.  Or feel free to post on
the list or mail me directly with questions/criticisms.

To refresh your memory, however, here's a recap of the meta-metamodel. 
I'm using the "new" meta-metamodel that resulted from my discussion with
Billy Tanksley, and I haven't heard back from him yet.  So this might
change a bit.  The basic principle of the Reflection metamodel will
remain the same, though.

* Prism's meta-metamodel defines a set of metamodels, which in turn
define sets of models, which represent computer programs.  A "metamodel"
is roughly analogous to a programming language.  In Prism, however,
metamodels are defined using three types of data structures -- _pBit,
_pStream, and _pMap -- rather than syntax.  When you define a metamodel,
you define how these three data structures may interact and what meaning
they have when they do.  Then a programmer models ("writes") a program
by defining specific values for the data structure.

* The _pBit data type is the fundamental unit of meaning.  It's either
'zero' or 'one.'  Those are just names, though -- they could just as
easily be 'true' and 'false' or 'Clinton' and 'Starr.'  (Men of the
Year, by the way -- is that a joke?)  Anyway, my point is that a _pBit
that has the value 'zero' doesn't necessarily represent the NUMBER
zero.  'Zero' and 'one' are just convenient names.

* The _pStream data type is an ordered collection of other data
structures.

* The _pMap data type is an indexed collection of other data
structures.  Values are indexed by _pStream data structures; the values
may be any type of data structure.

* Both collections (_pStream and _pMap) CONTAIN, not reference, their
contained data structures.  That means circular definitions are
impossible.  A collection may never contain itself.


The Global Heap

A brief word about Prism's global heap.  The global heap is an important
part of Prism, but it's also very simple.  It's just a _pMap datum which
persists for the life of the Prism compiler.  When you load Prism
models, they go on the global heap.  From there, they are translated to
a different metamodel ("compiled"), and the results go back on the
global heap.  Then the programmer can select specific models to save
back out to the disk, perhaps in binary form.

How are the models translated/compiled?  That's what the Reflection
Metamodel is for.


The Reflection Metamodel

"Reflection," as I'm sure you all know, is the ability of a program to
analyze and possibly modify itself.  Since Prism is a compiler
specification, "reflection" in the context of Prism means the ability to
analyze and modify the compiler.  That's what the Reflection metamodel
provides.  In essence, the Reflection metamodel provides a way for
programs to control how models are translated/compiled.  In fact, the
Reflection metamodel is the ONLY way in Prism that models may be
translated or compiled.

Here's a simple example.  The programmer could create a model in an
imperative metamodel (like C).  To translate it to machine code
("compile it"), he would start the Prism compiler and load his model
onto the global heap.  Then he would find a Reflection model which could
translate his C-like model to a machine-code model and would load THAT
onto the global heap as well.  Then he would tell the Prism compiler to
execute the Reflection model, using his C-like model as an input.

At this point, the Prism compiler would somehow execute the Reflection
model.  (In my compiler, I would simply interpret it.)  The Reflection
model (program, actually) would then grind away on the C-like model and
create a machine-code model, WITH IDENTICAL SEMANTICS.  (It's important
that the semantics not change. :) )

Once the Reflection program finished translating the C-like model into a
machine-code model, it would place the new machine-code model back on
the global heap.  The programmer would wake up from his nap :) and tell
the Prism compiler to output the new model, choosing "binary
executable"  as his syntax.

That's how the Reflection metamodel integrates with the rest of Prism. 
At this point, you may be wondering what the metamodel looks like.  I
sure hope you are, because that's what I'm about to describe.  :)

To use an analogy, the Reflection metamodel is like spaghetti-code
Basic.  It's an imperative metamodel which consists of a bag of
instructions.  After each instruction is executed/interpreted, the code
jumps to another instruction depending on whether the instruction
succeeded or failed.  Each instruction is very straightforward,
consisting of an opcode, a location for return values, a set of
parameters, and a comment.  Opcodes either manipulate the flow of the
Reflection program or data on the global heap.  Examples include:

* .pFlwBranchIf -- Conditionally branch to an instruction.
* .pGhGet -- Copy a datum from the global heap to the local heap.  (The
"local heap" is a _pMap datum analogous to the global heap, but whose
scope is limited to the life of a particular Reflection program.)
* .pGhSet -- Copy a datum from the local heap to the global heap.
* .pSmmBitSetOne -- Set a _pBit datum to the 'one' state.
* .pSmmMapGet -- Get a particular datum out of a _pMap datum.

The Reflection metamodel may seem to be extremely simple, providing a
pretty low-level domain abstraction.  That was intentional.  I wanted
the Reflection metamodel to provide minimal abstraction so that it would
be easy to implement.  Because Prism is all about domain abstraction,
abstracting the Reflection metamodel with a better metamodel shouldn't
be a problem.  :)

That's it!  Hopefully, that's a reasonably complete and understandable
description of the Reflection metamodel.  You can stop reading now, but
if you're interested, I've defined the exact structure and semantics of
the Reflection metamodel below.


Here's the structure:

_pStream (Reflection Model)
+---+----------------------------------------------------------------+
| 1 | _pStream: 32, _pBit (First Instruction)                        |
| 1 | _pStream: 32, _pBit (Double-Failure Instruction)               |
| 1 | _pMap (Instruction Bag)                                        |
|   | +-----------+-------------------------------------------------+|
|   | | _pStream: | _pStream (Instruction)                          ||
|   | | 32, _pBit | +---+------------------------------------------+||
|   | | (Instr    | | 1 | _pStream: 8, _pBit (Opcode)              |||
|   | | Index)    | | 1 | _pStream: 32, _pBit (Normal Branch)      |||
|   | |           | | 1 | _pStream: 32, _pBit (Failure Branch)     |||
|   | |           | | 1 | _pStream: 32, _pBit (Return Index)       |||
|   | |           | | 1 | _pStream (Parameter List)                |||
|   | |           | |   | +----+----------------------------------+|||
|   | |           | |   | | 0+ | _pStream (Parameter Reference)   ||||
|   | |           | |   | |    | +---+---------------------------+||||
|   | |           | |   | |    | | 1 | _pBit (Location Selector) |||||
|   | |           | |   | |    | | 1 | ANY (Parameter)           |||||
|   | |           | |   | |    | +---+---------------------------+||||
|   | |           | |   | +----+----------------------------------+|||
|   | |           | | 1 | _pStream (Comment)                       |||
|   | |           | |   | +----+----------------------------------+|||
|   | |           | |   | | 0+ | _pStream: 16, _pBit (Character)  ||||
|   | |           | |   | +----+----------------------------------+|||
|   | |           | +---+------------------------------------------+||
|   | +-----------+-------------------------------------------------+|
+---+----------------------------------------------------------------+
(For those of you who have been to my web site and looked at the Prism
spec, this rendition is a lot different than the one on the web site. 
I've made a couple of refinements, but more importantly, updated it for
the _pStream data type.)

I've also made a refinement to the visual language I use for Prism
metamodels.  If a _pStream datum consists only of _pBit data, I describe
it all on one line, rather than with a table.  So...

_pStream: 16, _pBit

...and...

_pStream
+----+-------+
| 16 | _pBit |
+----+-------+

...both refer to the same thing: a _pStream datum which consists of
exactly 16 _pBit data.


The semantics of the above structure are as follows:

A Reflection Model consists of a First Instruction, a Double-Failure
Instruction, and an Instruction Bag.  The First Instruction and
Double-Failure Instruction data each reference an Instruction datum in
the Instruction Bag.  First Instruction references the first instruction
which is executed in the program.  Double-Failure Instruction references
the instruction which is executed when an error occurs and the Failure
Branch datum (described below) is invalid.

An Instruction Bag consists of zero to (2^32)-1 Instructions.  Each
Instruction has a particular meaning which varies according to its
Opcode and Parameter List data.  After an Instruction is executed, the
"next" Instruction is executed.  The "next" Instruction is determined by
the Normal Branch or Failure Branch data.

An Instruction consists of an Opcode, a Normal Branch, a Failure Branch,
a Return Index, a Parameter List, and a Comment.  The action which
occurs when an Instruction is executed is defined by the Opcode datum. 
The Parameter List datum decides what the action affects and how.  The
Comment datum is ignored but is available for the programmer to use
(it's a Unicode string).  Normal Branch and Failure Branch were
described in the "Instruction Bag" description.

A Parameter List consists of zero or more Parameter References.  A
Parameter Reference consists of a Location Selector and a Parameter. 
The meaning of Parameter varies according to the value of Location
Selector.

If Location Selector is "one," then Parameter is a _pStream: 32, _pBit
which indexes a datum on the local heap.  The referenced datum is used
to modify the action prescribed by the Opcode in some way.  (The exact
way in which the Opcode is modified depends on the opcode.)

If Location Selector is "zero," then Parameter is any datum.  This datum
is used directly to modify the action prescribed by the Opcode in some
way.


I think that covers it.  This has been a long article, and I apologize
for that.  Even so, I didn't go into all of the details of the
Reflection Domain, like the specific semantics of the various opcodes
and their parameters.  For more example opcodes, check out the Prism
Specification on the Sphere web site.  The opcodes are at the bottom of
the document.  (See below for the address.)  The document is out of
date, unfortunately (it defines _pBits32 instead of _pStream in the
meta-metamodel), but the opcodes there should give you the flavor of
what I'm talking about.

Any comments?  I'd love to hear them.

Jim Little  (jiml@inconnect.com)
Prism is at http://www.teleport.com/~sphere.
The Prism Specification is at http://www.teleport.com/~sphere/000e/2.