// My Implementation of March 25 Assignment (mods in color)


// Mini OS, Semaphores Demo and MMU Test added -- 2004 March 24
//  Minimal March 25 Homework implementation
//  Additional MMU comments added in LoadProgram

import Files;
import Semaphores;
import Hardware;
import Dangerous;

// ==============================================================
// Global data, used as indicated..

// Semaphore Demo data..
final int Qsize = 4;
int[32] DemoFrame;  // stack frame for second process
char[Qsize] queue;  // the buffer
int fx=0, tx=0; // fill and take buffer indexes
Semaphore fill, take; // their semaphores

// MMU Test data..
int[32] PageTable;
int[40] AppPIB; // used for PIB + MMU

// Interrupt handler process stack frames..
int[16] MMUFrame;    // stack frame for MMU interrupt
int[16] ClockFrame;  // stack frame for Clock interrupt
int[16] DiskFrame;   // stack frame for Disk interrupt
int[16] KeyIntFrame; // stack frame for KeyIn interrupt
int[16] PrintFrame;  // stack frame for Print interrupt
int[16] Sema4Frame;  // stack frame for Signal/Wait interrupts
int[80] SysCallFrame; // stack frame for SysCall interrupt;
  // it's extra big because the SysCall handler calls lots of
  //   nested subroutines, which might collide with the next frame

// Data used by the file system..
int[16] ActiveFiles; // the 4 files you can have open, 4 ints each
final int Directory = 1536; // start of disk dir
  // watch for possible collision between this and main() stack
final int BuffersAt = Directory+256; // 256-byte buffers for files 1-4

// Memory allocator data..
int HeapTop = 16382; // allocates down from here..
final int HeapBottom = BuffersAt+1024; // .. to here

// Keyboard and Printer data..
int KeyFillPtr = BuffersAt, KeyTakePtr = BuffersAt,
  KeyShowPtr = BuffersAt; // initialize them to empty buffer
int GotRetns = 0; // the number of CRs in KeyIn buffer
boolean PrintReady = true; // =false between '\r' and interrupt

// Timer data..
int theTicks; // used by clock interrupt for Delay

// Process manager data..
int ReadyList = 0; // list of waiting processes
int CurProcess = 0; // whatever is currently running
int[8] MainPIB; // to make main() look like another process

// User-level file data..
FileId Kin, Cout; // KeyIn and ScreenOut, always open
String KeyString; // used by ReadStr() to buffer a line
final int[] MyName = { ORD('M')*256+ORD('i'), ORD('n')*256+ORD('i'),
   ORD('O')*256+ORD('S') }; // "MiniOS", the name of this file

// Video data, used within the system code..
int Vrow = 1, Vcol = 2; // current display V and H
final int TextColor = DoText+white;
final int TextBgnd = FillRect+minRed+minGreen+minBlue;
  // dark gray fill

final int[] FontBits = { 0, 0, 0, 0, 0,   0, 0, 190, 0, 0, // sp
   0, 14, 0, 14, 0,          40, 124, 40, 124, 40,
   76, 146, 511, 146, 100,   66, 37, 146, 328, 132,
   96, 150, 137, 118, 208,   0, 0, 14, 0, 0,
   0, 124, 130, 257, 0,      0, 257, 130, 124, 0,
   8, 42, 28, 42, 8,         16, 16, 124, 16, 16,
   0, 512, 448, 192, 0,      16, 16, 16, 16, 16,
   0, 192, 192, 0, 0,        0, 384, 96, 24, 6,
   56, 68, 130, 68, 56,      0, 4, 254, 0, 0,              // 0
   132, 194, 162, 146, 140,  68, 130, 146, 146, 108,
   48, 40, 36, 254, 32,      78, 138, 138, 138, 114,
   120, 148, 146, 146, 96,   2, 2, 226, 26, 6,
   108, 146, 146, 146, 108,  12, 146, 146, 82, 60,
   0, 204, 204, 0, 0,        0, 716, 460, 0, 0,
   0, 16, 40, 68, 130,       40, 40, 40, 40, 40,
   0, 130, 68, 40, 16,       4, 2, 162, 18, 12,
   120, 132, 306, 330, 60,   224, 56, 38, 56, 224,         // @A
   130, 254, 146, 146, 108,  124, 130, 130, 130, 68,
   130, 254, 130, 68, 56,    254, 146, 146, 130, 130,
   254, 18, 18, 18, 2,       124, 130, 130, 146, 244,
   254, 16, 16, 16, 254,     0, 130, 254, 130, 0,
   96, 128, 128, 128, 126,   254, 16, 40, 68, 130,
   254, 128, 128, 128, 128,  254, 4, 24, 4, 254,
   254, 4, 24, 32, 254,      124, 130, 130, 130, 124,
   254, 18, 18, 18, 12,      124, 130, 130, 386, 636,      // P
   254, 18, 50, 82, 140,     76, 146, 146, 146, 100,
   2, 2, 254, 2, 2,          126, 128, 128, 128, 126,
   14, 48, 192, 48, 14,      30, 224, 24, 224, 30,
   198, 40, 16, 40, 198,     6, 8, 240, 8, 6,
   194, 162, 146, 138, 134,  0, 0, 511, 257, 0,
   0, 6, 24, 96, 384,        0, 257, 511, 0, 0,
   4, 2, 1, 2, 4,            128, 128, 128, 128, 128,
   0, 2, 4, 0, 0,            64, 168, 168, 168, 240,       // `a
   254, 136, 136, 136, 112,  112, 136, 136, 136, 136,
   112, 136, 136, 136, 254,  112, 168, 168, 168, 48,
   16, 252, 18, 2, 4,        112, 648, 648, 648, 504,
   254, 8, 8, 8, 240,        0, 136, 250, 128, 0,
   512, 520, 506, 0, 0,      254, 16, 48, 72, 128,
   0, 2, 254, 0, 0,          248, 8, 240, 8, 240,
   248, 16, 8, 8, 240,       112, 136, 136, 136, 112,
   1016, 136, 136, 136, 112, 112, 136, 136, 136, 1016,     // p
   248, 16, 8, 8, 16,        144, 168, 168, 168, 72,
   0, 8, 126, 136, 128,      120, 128, 128, 128, 248,
   8, 48, 192, 48, 8,        56, 192, 48, 192, 56,
   136, 80, 32, 80, 136,     536, 608, 384, 96, 24,
   136, 200, 168, 152, 136,  0, 16, 238, 257, 0,
   0, 0, 511, 0, 0,          0, 257, 238, 16, 0,
   4, 2, 2, 4, 2 };

// ==============================================================
// Video driver subroutines, for use by the system only..

// This sets a simple rectangular clipping region
//  for concurrent programs sharing the screen by tiling..
void Clipper(int T, int L, int B, int R) {
  Video.top = T; // set clip region for this process
  Video.left = L;
  Video.bottom = B;
  Video.right = R;
  Video.datum = ClipRect;} // ~Clipper

// Erase next line, used when an output string has \r in it..
void ScreenLn() { // start new line, and clear it..
  Vcol = 2;
  Vrow = Vrow+12;
  if (Vrow>469) Vrow = 1;
  Video.left = 0;
  Video.right = 640;
  Video.top = Vrow-1;
  Video.bottom = Vrow+12;
  Video.datum = TextBgnd;} // ~ScreenLn

// Erase previous char, used when an output string has \b in it..
void ScreenBS() {
  if (Vcol<8) return; // nothing to back up over
  Video.right = Vcol;
  Vcol = Vcol-6;
  Video.left = Vcol;
  Video.top = Vrow;
  Video.bottom = Vrow+12;
  Video.datum = TextBgnd;} // ~ScreenBS

// Display one character..
void ScreenChar(char ch) { // display one character on current line
  if (ORD(ch)==13) ScreenLn();
  if (ORD(ch)==8) ScreenBS();
  if (ch<' ' || ch>'~' || Vcol>633) return; // not printable
  Video.address = ORD(ch)*10-320+&FontBits; // 5 ints x 2 bytes =10
  Video.left = Vcol;
  Video.right = Vcol+5;
  Vcol = Vcol+6;
  Video.top = Vrow;
  Video.bottom = Vrow+12;
  Video.datum = TextColor;} // ~ScreenChar

// File-oriented write to screen, called by SysCall(7)
//  when the file type is -1 (Screen/KeyIn); it assumes the data
//  is like String arrays, 1 char per 2-byte array element..
void WriteScr(int nby, int DataPtr) {
  while (nby>0) {
    ScreenChar(CHR(mmPEEK(DataPtr)));
    DataPtr = DataPtr+2;
    nby = nby-2;}} // ~WriteScr

// File-oriented read from keyin, called by SysCall(6)
//  when the file type is -1 (Screen/KeyIn); it packs data in
//  1 char per 2-bytes, like String arrays, omitting final return.
// Special-case: if buffer has only one element (2 bytes),
//  then a single return character is returned in it.
int ReadLine(int nby, int DataPtr) {
  // we want nby bytes (but not more than 254) in array DataPtr
  int got = 0; // the for-loop fill index
  int ch; // temp to hold current char
  boolean sho; // =true if need to display this char
  if (nby>254) nby = 254; // request is too big; trim it
  if (((KeyFillPtr-KeyTakePtr)&256)+KeyFillPtr-KeyTakePtr<nby
      && GotRetns==0) { // not enough chars to satisfy..
    while (KeyShowPtr != KeyFillPtr) { // echo what we have so far..
      ch = mmPEEK(KeyShowPtr);
      KeyShowPtr = ((KeyShowPtr+2)&254)+BuffersAt;
      ScreenChar(CHR(ch));}
    return -1;}
  while (got<nby) { // OK, we can do it this time..
    ch = mmPEEK(KeyTakePtr); // get a character from buffer
    sho = KeyShowPtr == KeyTakePtr; // false while working on the
      // keystrokes that were already echoed, then true after that.
    KeyTakePtr = ((KeyTakePtr+2)&254)+BuffersAt; // advance to next
    if (sho) { // once it's caught up, keep it up..
      ScreenChar(CHR(ch));
      KeyShowPtr = KeyTakePtr;}
    if (ch==13) { // keep returns count current..
      GotRetns = GotRetns-1;
      if (nby>2) break;} // exit if got end-of-line return
    else if (ch==8) if (nby>2) if (got>0) { // backspace..
      DataPtr = DataPtr-2; // back up over the previous character
      got = got-2;
      continue;} // don't insert it into buffer
    mmPOKE(DataPtr,ch); // '\r' isn't included unless asking for 1 byte
    DataPtr = DataPtr+2;
    got = got+2;} // '\r' will be last char in 1-byte request
  return got;} // ~ReadLine

// ==============================================================
// Disk file I/O, for use by the system only..

// Read or write one sector: flop =true if floppy; wrt =true to write;
//  get/put nby bytes at sector number sec, memory buffer at dPtr;
//  return the actual number of bytes transferred.
// Not interrupt-driven, it just busy-waits until completion.
// Called from various places inside the disk driver..
int RWsector(boolean flop, boolean wrt, int sec, int nby, int dPtr) {
  int timo = 65533; // hopefully long enough to wait
  if (flop && Enables.datum & FlopInBit ==0) return 0; // floppy not in
  if (flop) sec = ~sec; // hardware wants these negatives..
  if (wrt) nby = -nby;
  Disk.sector = sec;
  Disk.address = dPtr;
  Disk.count = nby;
  while (Enables.datum&DiskPendBit == 0 && timo !=0) // wait until done..
    timo = timo - timo/timo; // a little slower than just timo-1
  Enables.datum = IntOnBit; // turn DiskPendBit back off
  nby = nby-Disk.count;
  if (wrt) nby = -nby;
  return nby;} // ~RWsector

// Write nby bytes at address DataPtr to file index filix;
//  called by WriteFile from SysCall(7) for disk files.
// Does not properly map data through MMU!
int WrtDskFile(boolean flop, int filix, int nby, int DataPtr) {
  int sect = ActiveFiles[filix+1]; // get current file position
  int offs = ActiveFiles[filix+2];
  int BufPtr = filix*64+BuffersAt;
  int eofAt = ActiveFiles[filix+3]; // how many bytes left in this file
  int got = 0; // the # of bytes transferred
  if (nby>eofAt) if (eofAt>=0) nby = eofAt; // can't write past eof
  if (ActiveFiles[filix]+4==0) { // newly opened flop, or was reading..
    ActiveFiles[filix] = -5; // note this is 'write'
    if (offs>0) sect = sect-1;} // gotta rewrite this sector
  if (offs>0) { // partial buffer to fill..
    while (offs<256 && nby>0) { // transfer 2 bytes at a time
      POKE(BufPtr+offs, PEEK(DataPtr));
      offs = offs+2;
      DataPtr = DataPtr+2;
      nby = nby-2;
      got = got+2;
      eofAt = eofAt-2;}
    offs = offs & 255; // =0 when buffer is full
    if (offs==0) { // full: write sector out
      offs = RWsector(flop, true, sect, 256, BufPtr);
      sect = sect+1;
      offs = 0;}}
  // ASSERT: offs==0 || nby==0
  if (nby>255) { // write some whole sectors, directly from user buffer..
    offs = RWsector(flop, true, sect, nby&(-256), DataPtr);
    nby = nby-offs;
    got = got+offs;
    DataPtr = DataPtr+offs;
    eofAt = eofAt-offs;
    sect = offs/256+sect;
    offs = 0;}
  if (nby>0) { // buffer the rest (less than one sector)..
    offs = RWsector(flop, false, sect, 256, BufPtr);
    if (offs<256) nby = 0; // oops, something went wrong, kill it
    offs = 0;
    while (nby>0) {
      POKE(BufPtr+offs, PEEK(DataPtr));
      offs = offs+2;
      DataPtr = DataPtr+2;
      nby = nby-2;
      got = got+2;
      eofAt = eofAt-2;}
    sect = sect+1;}
  ActiveFiles[filix+1] = sect; // update file position info
  ActiveFiles[filix+2] = offs;
  ActiveFiles[filix+3] = eofAt;
  // ScreenChar('`');
  return got;} // ~WrtDskFile

// Read nby bytes to address DataPtr from file index filix;
//  called by ReadFile from SysCall(6)..
// Now properly maps addresses through MMU -- MAYBE
int RdDiskFile(boolean flop, int filix, int nby, int DataPtr) {
  int sect = ActiveFiles[filix+1]; // get current file position
  int offs = ActiveFiles[filix+2];
  int BufPtr = filix*64+BuffersAt;
  int eofAt = ActiveFiles[filix+3]; // how many bytes left in this file
  int got = 0; // the # of bytes transferred
  if (nby>eofAt) if (eofAt>=0) nby = eofAt; // can't read past eof
  if (offs>0) { // partial sector in buffer..
    while (offs<256 && nby>0) { // transfer 2 bytes at a time
      mmPOKE(DataPtr, PEEK(BufPtr+offs));
      offs = offs+2;
      DataPtr = DataPtr+2;
      nby = nby-2;
      got = got+2;
      eofAt = eofAt-2;}
    offs = offs & 255; // =0 when buffer is empty
    ActiveFiles[filix+2] = offs; // update file position info
    ActiveFiles[filix+3] = eofAt;
    if (offs==0) { // empty, advance sector pointer too
      sect = sect+1;
      ActiveFiles[filix+1] = sect;}}
  if (false) { // get some whole sectors, directly to user buffer..
    offs = RWsector(flop, false, sect, nby&(-256), DataPtr);
    nby = nby-offs;
    got = got+offs;
    DataPtr = DataPtr+offs;
    eofAt = eofAt-offs;
    sect = offs/256+sect;
    ActiveFiles[filix+1] = sect;}
  while (nby>0) { // buffer the rest (less than one sector)..
    offs = RWsector(flop, false, sect, 256, BufPtr);
    if (offs<256) if (offs<nby) nby = offs; // maybe early eof?
    offs = 0;
    while (nby>0 && offs<256) {
      mmPOKE(DataPtr, PEEK(BufPtr+offs));
      offs = offs+2;
      DataPtr = DataPtr+2;
      eofAt = eofAt-2;
      if (eofAt+1==0) eofAt = 0; // don't cross 0
      nby = nby-2;
      got = got+2;}
    offs = offs & 255; // =0 when buffer is empty
    if (offs==0) sect = sect+1;
    ActiveFiles[filix+2] = offs; // update file position info
    ActiveFiles[filix+3] = eofAt;
    ActiveFiles[filix+1] = sect;}
  return got;} // ~RdDiskFile

// Look for an unused file index, called by Open only..
int FindAvailFile() {
  if (ActiveFiles[4]==0) return 4;
  if (ActiveFiles[8]==0) return 8;
  if (ActiveFiles[12]==0) return 12;
  return 0;} // ~FindAvailFile

// 2 functions to pack and unpack bytes,
//   between char[] arrays (2 bytes for each character)
//   and disk file format (byte for byte)
//
// These are called by the file manager to convert between String
//  file names and the packed form of the directory.
// You could use them elsewhere, but they take integer pointers,
//  which requires using the (unsafe) unary-& operator.

// Pack nby bytes from char[] array frm to int[] array too
//   bytes are 1/word in char[], 2/word in int[]
void PackBytes(int nby, int frm, int too) {
  int temp;
  while (nby>0) {
    temp = mmPEEK(frm)*256;
    nby = nby-2;
    frm = frm+2;
    if (nby>=0) temp = temp + (mmPEEK(frm)&255);
    mmPOKE(too,temp);
    too = too+2;
    frm = frm+2;}} // ~PackBytes

// Unpack nby bytes from int[] array frm to char[] array too
//   bytes are 1/word in char[], 2/word in int[]
void unPackBy(int nby, int frm, int too) {
  int temp;
  while (nby>0) {
    mmPOKE(too,mmPEEK(frm-1)&255);
    mmPOKE(too+2,mmPEEK(frm)&255);
    nby = nby-2;
    frm = frm+2;
    too = too+4;}} // ~unPackBy

// Compare the String name at NamePtr to dirName (in the directory),
//  return true if they are equal (not case-sensitive);
//  NamePtr points to 1st byte of user String,
//  dirName points to a name in directory, max 27 chars;
// Called by OpenFile to find an existing file with this name..
boolean EqualName(int dirName, int NamePtr) {
  int ix = mmPEEK(NamePtr-2)/2; // get String length
  int chd, chn;
  if (ix>27) return false; // NamePtr is too long, cannot be equal
  if ((mmPEEK(dirName+3+ix)&255)!=0) return false; // too short
  while (ix>0) {
    ix = ix-1;
    chd = mmPEEK(dirName+3+ix)&255;
    if (chd<91) if (chd>64) chd = chd+32;
    chn = mmPEEK(NamePtr+ix+ix);
    if (chn<91) if (chn>64) chn = chn+32;
    if (chd != chn) return false;} // unequal
  return true;} // ~EqualName

// Store file type fity into file index fid,
//  then set start sector and eof from directory entry # fino;
//  return a FileID value.
// Called by OpenFile() on its way back to SysCall(4)
int SetActiveFile(int fid, int fino, int fity) {
  if (fity>0) ActiveFiles[fid] = fity;
  ActiveFiles[fid+1] = mmPEEK(Directory+fino*32);
  ActiveFiles[fid+2] = 0;
  ActiveFiles[fid+3] = mmPEEK(Directory+fino*32+2);
  return fid/4+1;} // ~SetActiveFile

// Open any known named file or file type
int OpenFile(int NamePtr) { // called by SysCall(4)
  char two = CHR(mmPEEK(NamePtr+2)&223); // 2nd char of name, if needed
  int fi = FindAvailFile(); // might not be needed, but cheap call
  int ix = -1; // = default device name, KeyIn/ScreenOut
  int nx;
  if (mmPEEK(NamePtr)==ORD('.')) { // device name..
    if (two == 'K') fi = 0; // KeyIn/ScreenOut are always FileId =1
    else if (two == 'S') fi = 0;
    else if (fi==0) return 0; // FindAvailFile() failed, can't open
    else if (two == 'P') ix = -2; // printer
    else if (two == 'F') ix = -4; // floppy
    else if (two == 'H') ix = -3; // hard-sectored hard disk
      else return 0; // dunno; can't open it
    ActiveFiles[fi] = ix; // set the file type
    if (ix+4 == 0) { // floppy, read the directory..
      ix = RWsector(true, false, 0, 256, Directory);
      // you should verify file name, etc...
      return SetActiveFile(fi,1,0);} // we just open file # 1
    else if (ix+3 == 0) { // hard disk, absolute sector..
      ActiveFiles[fi+1] = 0; // sector 0
      ActiveFiles[fi+2] = 0;
      ActiveFiles[fi+3] = -1;} // no size limit
    return fi/4+1;}
  else { // named HD file..
    if (mmPEEK(NamePtr-2)>54) return 0; // name too long
    if (RWsector(false, false, 0, 256, Directory)<256)
      return 0; // read failed
    ix = mmPEEK(Directory+12); // # files on this disk
    while (ix>0) // try each file..
      if (EqualName(Directory+ix*32,NamePtr))
        return SetActiveFile(fi,ix,ix); // found it
      else ix = ix-1;
    ix = mmPEEK(Directory+12)+1; // not there, add another file
    if (ix>mmPEEK(Directory+14)) return 0; // oops, too many
    nx = mmPEEK(NamePtr-2)/2; // length of file name, in chars
    PackBytes(nx, NamePtr, Directory+ix*32+4); // insert name
    mmPOKE(Directory+ix*32+4+nx,0); // null terminated
    mmPOKE(Directory+ix*32,mmPEEK(Directory+10)); // set start to next sec
    mmPOKE(Directory+ix*32+2,-1); // new file, assume 65K
    if (RWsector(false, true, 0, 256, Directory)==256) // rewrite dir
      return SetActiveFile(fi,ix,ix);} // fail if dir rewrite fails
    return 0;} // ~OpenFile

// Close the file indexed by FileId theFile..
void CloseFile(int theFile) { // called by SysCall(5)
  int fi = theFile*4-4; // point to the indexed ActiveFiles
  int BufPtr = fi*64+BuffersAt; // point to its buffer, in case needed
  int ix;
  if (fi<0) return; // not an open file (null FileId)
  if (theFile>4) return; // not an open file (bogus FileId)
  ix = ActiveFiles[fi]; // the file # on disk, or device #
  if (ix>0) { // close hard disk file..
    ActiveFiles[fi] = 0;
    if (RWsector(false, false, 0, 256, Directory)<256) return;
    if (mmPEEK(Directory+12)>=ix) return; // not closing new file
    if (ActiveFiles[fi+3]+1==0) return; // didn't write anything
    mmPOKE(Directory+12,ix); // update # files
    mmPOKE(Directory+ix*32+2,~ActiveFiles[fi+3]); // actual file size
    ix = ActiveFiles[fi+1]; // this is next sector to write
    if (ActiveFiles[fi+2]>0) // rewrite partial buffer
      if (RWsector(false, true, ix, 256, BufPtr)==256)
        ix = ix+1; // successful write, update sector #
    mmPOKE(Directory+10,ix); // that is sector for next file to open
    ix = RWsector(false, true, 0, 256, Directory); // rewrite dir
    return;}
  else if (ix+5==0) if (ActiveFiles[fi+2]>0) // writing to floppy..
    ix = RWsector(true, true, ActiveFiles[fi+1], 256, BufPtr);
  if (fi>0) ActiveFiles[fi] = 0; // leave K/S open
  } // ~CloseFile

// Read a block into array DataPtr from open FileId theFile..
int ReadFile(int theFile, int DataPtr) { // called by SysCall(6)
  int fi = theFile*4-4; // point to the indexed ActiveFiles
  int who, len;
  if (fi<0) return 0; // not an open file (null FileId)
  if (theFile>4) return 0; // not an open file (bogus FileId)
  who = ActiveFiles[fi];
  if (who==0) return 0; // not an open file (stale fileId)
  if (DataPtr==0) return 0; // no array to read into
  len = mmPEEK(DataPtr-2); // the size of the array
  if (who+1==0) return ReadLine(len, DataPtr); // Keyboard input
  if (who+4==0) return RdDiskFile(true, fi, len, DataPtr); // Floppy in
  if (who>0) { // named HD file
    if (ActiveFiles[fi+3]+1==0) return 0; // new file, nothing to read
    who = -3;}
  if (who+3==0 || who>0) // Hard disk sector or file input..
    return RdDiskFile(false, fi, len, DataPtr);
  return 0; // ignore the rest
  } // ~ReadFile

// Write a block from array DataPtr to open FileId theFile..
int WriteFile(int theFile, int DataPtr) { // called by SysCall(7)
  int fi = theFile*4-4; // point to the indexed ActiveFiles
  int who, len;
  if (fi<0) return 0; // not an open file (null FileId)
  if (theFile>4) return 0; // not an open file (bogus FileId)
  who = ActiveFiles[fi];
  if (who==0) return 0; // not an open file (stale fileId)
  if (DataPtr==0) return 0; // no array to write from
  len = mmPEEK(DataPtr-2); // the size of the array
  if (who+1==0) { // Screen output..
    WriteScr(len, DataPtr);
    return len;}
  if (who != -2) // Floppy/hard disk output..
    return WrtDskFile(who<-3, fi, len, DataPtr);
  return 0; // ignore the rest
  } // ~WriteFile

// ==============================================================

// Process management subroutine,
//  moves CurProcess to Blok2, and ReadyList to CurProcess
int SwapOut(int SemSP, int Blok2) {
  int going = CurProcess;
  int comin = ReadyList;
  if (Blok2==0) Blok2 = &ReadyList; // Blok2=0 just swaps
  if (comin !=0) if (going !=0) { // can't do it if nothing there
    ReadyList = PEEK(comin); // unlinked top of ReadyList
    CurProcess = comin; // and make it current
    POKE(going+6,SemSP); // save current process's SP in its PIB
    POKE(going, PEEK(Blok2)); // link it to rest of its list
    POKE(Blok2,going); // move (formerly) CurProcess to its list
    MMU.address = PEEK(comin+4); // set MMU to current
    return PEEK(comin+6);} // exit to it on caller's CoCall
  return SemSP;} // ~SwapOut

// ==============================================================
// Interrupt drivers..
// These are called ONCE to start up their respective processes.
//  After that, they do all their work when interrupts hit..

void DoSema4() {
  // These 2 interrupts happen for Signal and Wait respectively
  int SemSP = 0;  // maps to Sema4Frame[3]
  int aPtr, temp;
  Sema4Frame[6] = &Sema4Frame[9];   // initial SP
  Sema4Frame[8] = &Sema4Frame[0];   // initial FP
  Sema4Frame[0] = PEEK((&SemSP)-6); // copy my globals ptr
  Sema4Frame[1] = 0;        // halt if it returns (it shouldn't)
  SemSP = FORK(&Sema4Frame[6]);  // returns twice:
  if (SemSP==0) { // returned in caller's process..
    aPtr = &Sema4Frame[3]; // set 2 interrupt vector items..
    iVector[6] = aPtr;
    iVector[7] = aPtr;
    return;}
  // otherwise returned in new process (first)..
  POKE(SemSP-6,0);  // clear caller's return value
  while (true) {  // this is the interrupt handler loop..
    COCALL(&SemSP);  // wait for interrupt
    aPtr = SignalWait.address; // the semaphore (not on stack)
    if (mmPEEK(mmPEEK(SemSP)-2)&1 ==0) { // caller's opcode, =0 if Signal
      temp = ReadyList;       // get previous ready list,
      ReadyList = mmPEEK(aPtr); // replace it with process from sem,
      mmPOKE(aPtr,mmPEEK(ReadyList)); // (delete top of sem queue)
      mmPOKE(ReadyList, temp);} // ..link ready list to previous
    else SemSP = SwapOut(SemSP,aPtr);} // otherwise it's a Wait
  } // ~DoSema4

void DoDisk() {
  // This interrupt is used for switching context at the end of
  //   disk I/O, if you switched out to make use of the wait time.
  // Right now it's set up but unused.
  int SemSP = 0;  // maps to DiskFrame[3]
  int unused;
  int temp;
  DiskFrame[6] = &DiskFrame[9];  // initial SP
  DiskFrame[8] = &DiskFrame[0];  // initial FP
  DiskFrame[0] = PEEK((&SemSP)-6);  // copy my globals ptr
  DiskFrame[1] = 0;        // halt if it returns (it shouldn't)
  SemSP = FORK(&DiskFrame[6]);  // returns twice:
  if (SemSP==0) { // returned in caller's process..
    iVector[2] = &DiskFrame[3]; // set interrupt vector
    return;}
  // otherwise returned in new process (first)..
  POKE(SemSP-6,0);  // clear caller's return value
  while (true) {  // this is the interrupt handler loop..
    COCALL(&SemSP);  // wait for interrupt
      // YOUR CODE GOES HERE
    } // do nothing
  } // ~DoDisk

void DoKeyInts() {
  // This interrupt grabs a keystroke when it is available,
  //   and saves it in a buffer for when the program needs it.
  int SemSP = 0;  // maps to KeyIntFrame[3]
  int theKey;
  int temp;
  KeyIntFrame[6] = &KeyIntFrame[9];  // initial SP
  KeyIntFrame[8] = &KeyIntFrame[0];  // initial FP
  KeyIntFrame[0] = PEEK((&SemSP)-6);  // copy my globals ptr
  KeyIntFrame[1] = 0;                 // halt if it returns
  SemSP = FORK(&KeyIntFrame[6]); // create a new process (2 returns)
  if (SemSP==0) { // final return in initial process
    iVector[3] = &KeyIntFrame[3]; // set interrupt vector
    return;}
  // otherwise returned in new process (first)..
  POKE(SemSP-6,0);  // clear caller's return value
  while (true) { // this is the interrupt handler loop..
    COCALL(&SemSP);  // wait for interrupt
    theKey = KeyIn.datum & 127; // got a keystroke, save it..
    mmPOKE(KeyFillPtr, theKey); // ..in the keyboard buffer
    if (theKey==13) GotRetns = GotRetns+1; // count returns (=lines)
    KeyFillPtr = ((KeyFillPtr+2)&254)+BuffersAt; // advance ptr
    if (KeyFillPtr==KeyTakePtr) { // buffer o'flow, discard oldest
      if (mmPEEK(KeyTakePtr)==13) GotRetns = GotRetns-1;
      KeyTakePtr = ((KeyTakePtr+2)&254)+BuffersAt;
      if (KeyFillPtr==KeyShowPtr) // discard oldest show also..
        KeyShowPtr = KeyTakePtr;}
    } // ~while
  } // ~DoKeyInts

void DoPrint() {
  // This interrupt happens after line output completion
  //   Right now it's set up but unused.
  int SemSP = 0;  // maps to PrintFrame[3]
  int unused;
  int temp;
  PrintFrame[6] = &PrintFrame[9];  // initial SP
  PrintFrame[8] = &PrintFrame[0];  // initial FP
  PrintFrame[0] = PEEK((&SemSP)-6);  // copy my globals ptr
  PrintFrame[1] = 0;        // halt if it returns (it shouldn't)
  SemSP = FORK(&PrintFrame[6]);  // returns twice:
  if (SemSP==0) { // returned in caller's process..
    iVector[4] = &PrintFrame[3]; // set interrupt vector
    return;}
  // otherwise returned in new process (first)..
  POKE(SemSP-6,0);  // clear caller's return value
  while (true) {  // this is the interrupt handler loop..
    COCALL(&SemSP);  // wait for interrupt
    PrintReady = true;} // do nothing
  } // ~DoPrint

void DoMemMgr() {
  // This interrupt happens on a memory manager page fault
  //   Right now ...
  int SemSP = 0;  // maps to MMUFrame[3]
  int page;
  int temp;
  MMUFrame[6] = &MMUFrame[9];  // initial SP
  MMUFrame[8] = &MMUFrame[0];  // initial FP
  MMUFrame[0] = PEEK((&SemSP)-6);  // copy my globals ptr
  MMUFrame[1] = 0;        // halt if it returns (it shouldn't)
  SemSP = FORK(&MMUFrame[6]);  // returns twice:
  if (SemSP==0) { // returned in caller's process..
    iVector[0] = &MMUFrame[3]; // set interrupt vector
    return;}
  // otherwise returned in new process (first)..
  POKE(SemSP-6,0);  // clear caller's return value
  while (true) {  // this is the interrupt handler loop..
    iVector[0] = &MMUFrame[3]; // OK, put it back
    COCALL(&SemSP);  // wait for interrupt
    iVector[0] = 0;  // so it crashes "Semaphor=0" if still NG
    page = ((MMU.fault >> 10) & 63);
    temp = PEEK(CurProcess+4)-1; // address of MMU for this process
    if ((PEEK(temp+page) & 255) == 126) { // unmapped..
      POKE(temp+page, PEEK(temp+page)-126+page+0xA0); // turn it on
      if (MMU.PC != 0) { // stack faulted, restore saved values..
        mmPOKE(SemSP,MMU.PC);
        mmPOKE(SemSP-2,MMU.FP);
        mmPOKE(SemSP-4,MMU.CB);}}
    else mmPOKE(SemSP,0); // otherwise kill it, PC=0
    } // do something..
  } // ~DoMemMgr

void DoClock() {
  // This interrupt happens 8x each second, for timing purposes;
  //   we use it to maintain theTicks.
  int SemSP = 0;  // maps to ClockFrame[3]
  int prior; // holds previous Date.seconds reading, updated
  int temp;
  ClockFrame[6] = &ClockFrame[9];  // initial SP
  ClockFrame[8] = &ClockFrame[0];  // initial FP
  ClockFrame[0] = PEEK((&SemSP)-6);  // copy my globals ptr
  ClockFrame[1] = 0;        // halt if it returns (it shouldn't)
  SemSP = FORK(&ClockFrame[6]);  // returns twice:
  if (SemSP==0) { // returned in caller's process..
    iVector[1] = &ClockFrame[3]; // set interrupt vector
    return;}
  // otherwise returned in new process (first)..
  POKE(SemSP-6,0);  // clear caller's return value
  while (true) {  // this is the interrupt handler loop..
    COCALL(&SemSP);  // wait for interrupt
    temp = Date.seconds;
    if (temp != prior) { // ticks management, got a new second..
      prior = temp;
      theTicks = prior*8;}
    else theTicks = theTicks+1;
    // YOUR CODE GOES HERE
  } } // ~DoClock

void DoSysCall() {
  // this interrupt happens when the program executes SysCall
  int SemSP = 0;  // maps to SysCallFrame[3]
  int aPtr;
  int temp;
  SysCallFrame[6] = &SysCallFrame[9];  // initial SP
  SysCallFrame[8] = &SysCallFrame[0];  // initial FP
  SysCallFrame[0] = PEEK((&SemSP)-6);  // copy my globals ptr
  SysCallFrame[1] = 0;                 // halt if it returns
  SemSP = FORK(&SysCallFrame[6]); // create a new process (2 returns)
  if (SemSP==0) { // final return, into initial process
    iVector[5] = &SysCallFrame[3]; // ready, set interrupt vector
    return;}
  // otherwise returned in new process (first)..
  POKE(SemSP-6,0);  // clear caller's return value
  while (true) { // this is the interrupt handler loop..
    iVector[5] = &SysCallFrame[3]; // restore double-fault crasher
    COCALL(&SemSP);  // wait for interrupt
    iVector[5] = 0;  // so it crashes "Semaphor=0" on programming error
    temp = mmPEEK(SemSP-8); // get a parameter (the last pushed)
    switch(mmPEEK(SemSP-6)) { // get opcode
    case 0:                                   // exit from program
      SemSP = SwapOut(SemSP,0); // Batch: let main take care of it
      break;
    case 1:                                   // array bounds check
      temp = mmPEEK(0xFABC);  // non-existing memory, to crash
      mmPOKE(SemSP,0);  // clear caller's PC
      break;
    case 2:                                   // memory allocator
      temp = temp+4; // # bytes wanted
      if (HeapBottom+temp<HeapTop) { // do we have enough available?
        aPtr = HeapTop-temp; // yup, allocate it..
        HeapTop = aPtr;
        temp = temp-4;
        mmPOKE(aPtr+2,temp); // record the size
        mmPOKE(aPtr,0);  // initially zero references
        aPtr = aPtr+4;} // the front of the data proper
      else aPtr = 0; // otherwise return null
      mmPOKE(SemSP-8,aPtr); // return pointer, or null
      break;
    case 3:                          // memory de-allocator request
      if (temp == 0) continue; // ignore null (initial assignment)
      temp = temp-4; // the pointer's ref count is here
      if (mmPEEK(temp) & -2 == 0) {
        mmPOKE(temp,0); // take count down to 0 for viewing
        } // dispose it (You get to write this part)
      else if (mmPEEK(temp) & -16 == 0) { // not permanent..
        mmPOKE(temp,mmPEEK(temp)-1);} // decrement reference count
      break; // caller pops the pointer
    case 4:                                   // open file
      mmPOKE(SemSP-8, OpenFile(temp)); // OpenFile does the work
      break;
    case 5:                                   // close file
      CloseFile(temp); // CloseFile does the work
      break;
    case 6:                                   // read file
      temp = ReadFile(mmPEEK(SemSP-10), temp); // ReadFile does it
      if (temp<0) // the device is busy, so..
        mmPOKE(SemSP,mmPEEK(SemSP)-1); // back up PC to try again
        // better: block this process until it's unbusy,
        //   but we don't have processes implemented yet
      else mmPOKE(SemSP-10, temp); // the return value (how many bytes)
      break;
    case 7:                                   // write file
      temp = WriteFile(mmPEEK(SemSP-10), temp);
      if (temp<0) mmPOKE(SemSP,mmPEEK(SemSP)-1); // back up PC
        else mmPOKE(SemSP-10, temp);
      break;
    case 8:                                   // seek file
      // this is left to the student as an exercise
      break;
    case 9:                                   // get file info
      mmPOKE(SemSP-8, 0); // info? what info? return 0
      break;
    case 13:            // wait for event (yield to other process)
      SemSP = SwapOut(SemSP,0);
      break;
    case 14:                                   // ticks delay
      if (temp<=0) { // asking for a delay (>0 is target time)
        temp = theTicks-temp;
        mmPOKE(SemSP-8,temp);}
      if (temp>theTicks)
        mmPOKE(SemSP,mmPEEK(SemSP)-1); // back up PC to wait some more
      break;
    case 17:                                   // set MMU
      temp = mmPEEK(SemSP-8); // new MMU setting
      POKE(CurProcess+4,temp); // save in PIB
      MMU.address = temp; // make it happen
      break;
    }} // ~while ~switch // otherwise do nothing
  } // ~DoSysCall

// ==============================================================
// User-level system code..

// Look to see if HD is formatted, and if not, build directory
void HDformat(boolean forced) { // if forced=true, build it regardless
  int ix;
  int[128] Buff; // one sector, for the 7-file directory.
  FileId HD = Open(".H"); // all I/O is at user level!
  ix = Read(HD,Buff); // reads sector 0 into Buff
  Close(HD);
  if (!forced) if (ix==256) if (Buff[0]==ORD('H')*256+ORD('a'))
    if (Buff[1]==ORD('r')*256+ORD('d'))
    if (Buff[2]==ORD('D')*256+ORD('i'))
    if (Buff[3]==ORD('s')*256+ORD('k')) return; // it seems OK
  ix = 128; // no, set up my 1-sector directory..
  while (ix>0) {ix = ix-1; Buff[ix] = 0;} // clear the buffer,
  Buff[0] = ORD('H')*256+ORD('a'); // then insert signature,
  Buff[1] = ORD('r')*256+ORD('d');
  Buff[2] = ORD('D')*256+ORD('i');
  Buff[3] = ORD('s')*256+ORD('k');
  Buff[4] = 0; // no boot sector
  Buff[5] = 4; // next unused sector
  Buff[6] = 0; // no files yet
  Buff[7] = 7; // directory size (could be 31 in 4 sectors)
  HD = Open(".H"); // OK, rewrite the directory
  ix = Write(HD,Buff); // no error checking!
  Close(HD);} // ~HDformat

// Read one line (up to \r) from keyboard, return it as String
String ReadStr() { // return a pointer to an input line
  int ix = 0, nx = 0;
  String theStr;
  int theLen;
  theLen = Read(Kin, KeyString)/2; // get a line
  theStr = new char[theLen]; // allocate as many bytes as we got
  while (ix<theLen) { // copy them over to new String array..
    theStr[ix] = KeyString[ix];
    ix = ix+1;}
  return theStr;} // ~ReadStr

// Display output String,
//  a cleaner interface than needing to deal with returned int..
void PrintStr(String str) {int ix = Write(Cout, str);}

// Display output character..
void PrintChar(char ch) { // uses SysCall, for safety
  int nx;
  char[1] cc; cc[0] = ch;
  nx = Write(Cout, cc);}

// PrintNum, a string-free number display (uses PrintChar)
void PrintNum(int theNum) {
  if (theNum==32768) { // special-case, it has no positive..
    PrintChar('-');
    PrintChar('3');
    theNum = 2768;}
  else if (theNum<0) { // negative number, show the sign
    PrintChar('-');
    theNum = -theNum;}
  if (theNum>9) { // recursive call to do leftmost digits first
    PrintNum(theNum/10);
    theNum = theNum%10;}
  PrintChar(CHR(theNum+48));} // ~PrintNum

// Halt if stack overrun. Called by main() or a deep subroutine
//  called from main(), in case I counted wrong in space allocation..
void BombCheck() {
  int here; // a local address to test
  if (&here>Directory-100) { // oops, this is a crasher...
    PrintStr("** Stack Collision **");
    here = SysOp0(0);}} // ~BombCheck

// This is the startup code that get the OS running.
// Call it once, before anything else..
void MiniOS() {
  MMU.address = 0; // turn MMU off
  BombCheck(); // verify that we have enough stack space to do this
  POKE(Directory-2,256); // make it look like array
  POKE(Directory-4,-16);
  DoSysCall(); // initialize each type of interrupt..
  DoKeyInts();
  DoDisk();
  DoPrint();
  DoClock();
  DoSema4();
  DoMemMgr();
  MainPIB[0] = 0; // create a (fake) PIB for current process..
  MainPIB[2] = 0; // my MMU initially disabled
  MainPIB[4] = 0;
  CurProcess = &MainPIB;
  Enables.datum = IntOnBit; // enables interrupts (reset turned 'em off)
  HDformat(false); // check for hard drive & initialize
  Kin = Open(".K"); // open default KeyIn & ScreenOut files..
  Cout = Open(".S");
  KeyString = new char[120]; // used by ReadStr()
  do PrintChar('\r'); while (Vrow>2); // clear screen (interrupt-safe)
  } // ~MiniOS

// Some String operators...

// Return true if str1==str2 (not case sensitive)..
boolean StrEqual(String str1, String str2) {
  int ix = LENGTH(str2);
  char ch1, ch2;
  if (LENGTH(str1) != ix) return false;
  while (ix>0) {
    ix = ix-1;
    ch1 = str1[ix];
    if (ch1>='A') if (ch1<='Z') ch1 = CHR(ORD(ch1)+32);
    ch2 = str2[ix];
    if (ch2>='A') if (ch2<='Z') ch2 = CHR(ORD(ch2)+32);
    if (ch1 != ch2) return false;}
  return true;} // ~StrEqual

// Parse out word wdno from aStr..
String GetWord(String aStr, int wdno) {
  String res;
  boolean inwd = false;
  int here = 0, bgn = -1, end = -1;
  while (here<LENGTH(aStr)) {
    if ((aStr[here]>' ') != inwd) {
      inwd = !inwd;
      if (inwd) {
        if (wdno==0) bgn = here;
        wdno = wdno-1;}
      else if (wdno<0) break;}
    here = here+1;}
  if (wdno<0) end = here;
  if (bgn<end) {
    res = new char[end-bgn];
    here=0;
    while (bgn<end) {
      res[here] = aStr[bgn];
      here = here+1;
      bgn = bgn+1;}
    return res;}
  else return "";} // ~GetWord

// Some date extraction routines..
// These extract date parts from ints; they do not access the I/O

int YearOf(int datehours) {return (datehours>>1)/4383;}
 // returns 4 for 2004
int HourOf(int datehours) {return (datehours-24000)%24;}
int JulianOf(int datehours) {return (datehours-YearOf(datehours)*8766)/24;}
int MonthDayOf(int datehours) { // computes both month and day..
  final int[] mons = {31,28,31,30,31,30,31,31,30,31,30,31};
  int yr = YearOf(datehours); // so we know if it's leapyear
  int dy = JulianOf(datehours);
  int mo = 0;
  while (dy>=mons[mo]) { // count off the months..
    dy = dy-mons[mo];
    mo = mo+1;
    if (mo==2) dy = dy-1;}
  return mo*100+dy+101;} // ~MonthDayOf
int MonthOf(int datehours) {return MonthDayOf(datehours)/100;}
int DayOf(int datehours) {return MonthDayOf(datehours)%100;}

// This cheats a little, by reaching directly into the directory,
//  to extract the name of the recently-opened floppy disk file (fno=0),
//  or the name of HD file #fno.
String GetFileName(int fno) { // return String name of the file #fno
  int ix = 0;
  String theStr;
  char[28] theName;
  int[] DirAry; // will point to Directory
  int theLen = 27;
  FileId fi;
  if (fno>0) { // HD file name..
    POKE(&DirAry,Directory);
    fi = Open(".H"); // opens the HD directory
    ix = Read(fi,DirAry);
    fno = fno-1;}
  else fi = Open(".F"); // opens the floppy directory
  unPackBy(28, Directory+fno*32+36, &theName); // get name, padded by nulls
  while (theLen>=0) {
    if (theName[theLen]>' ') break;
    theLen = theLen-1;}
  theStr = new char[theLen+1]; // allocate as many bytes as we got
  ix = 0;
  while (ix<=theLen) { // copy them over to new String array..
    theStr[ix] = theName[ix];
    ix = ix+1;}
  Close(fi);
  return theStr;} // ~GetFileName

// Returns true if current floppy is "MiniOS"
// Used to skip user interaction after flop is changed,
//  and to force DH reformat if not.
boolean MyFlopName() {
  int nx = LENGTH(MyName); // = "MiniOS" packed as int[]
  int ix = 0;
  FileId fi = Open(".F"); // opens the floppy directory
  while (ix<nx) { // compare it to "MiniOS"
    if (MyName[ix] != PEEK(Directory+36+ix*2)) nx = -1; // unequal (exit)
    ix = ix+1;}
  Close(fi);
  return nx>0;} // ~MyFlopName

// If current floppy is "MiniOS", asks for new;
//  returns true if current floppy is still "MiniOS"
boolean FlopSwap() {
  if (MyFlopName()) {
    PrintStr("Mount floppy, then click\r");
    while (Mouse.datum>=0) ;
    while (Mouse.datum<0) ;}
  return MyFlopName();} // ~FlopSwap

// Show HD directory..
void ShowDir() {
  String str;
  int fno = 0;
  while (true) {
    fno = fno+1;
    str = GetFileName(fno);
    if (LENGTH(str)==0) return;
    PrintStr("\r");
    PrintNum(fno);
    PrintStr(" ");
    PrintNum(PEEK(Directory+fno*32));
    PrintStr("  ");
    PrintNum(PEEK(Directory+fno*32+2));
    PrintStr("  ");
    PrintStr(str);}} // ~ShowDir

// Copy one (program) file from floppy to hard disk..
void CopyFlop() {
  String str;
  FileId fi, fo;
  int[128] buffer;
  int nby, totby = 0;
  BombCheck();
  if (FlopSwap()) HDformat(true); // erase hard drive
  str = GetFileName(0); // the name on disk will be same as on floppy
  PrintStr("Copying file '");
  PrintStr(str);
  PrintStr("' ");
  fi = Open(".F"); // open floppy source
  fo = Open(str); // open destination file, same name
  nby = (Read(fi, buffer)+1)/2; // read a block
  totby = buffer[0]; // get the byte count
  PrintNum(totby);
  PrintStr(" bytes ");
  while (totby>0 && nby>0) {
    while (nby<128) {buffer[nby] = 0; nby = nby+1;} // fill out partial
    if (nby*2 > Write(fo, buffer)) { // write it
      PrintStr("Write Error ");
      break;}
    PrintChar('.'); // one dot every block
    totby = totby-nby*2; // count bytes down
    nby = (Read(fi, buffer)+1)/2;} // read next block
  Close(fi);
  Close(fo);
  PrintStr(" Done.\r");} // ~CopyFlop


// Load a program file, rewritten for Mar25 assignment..
void LoadProgram(String prog) {
  final int maxCode = 1024; // punt: only read programs < than this
  String str;
  FileId fi;
  int CodeBase, Stack;
  int[] DiskBlk = new int[maxCode]; // a low-memory disk buffer
  int nx = 0xC0C1, ix = 32;
  BombCheck();
  while (ix>0) { // setup working MMU = writeable real
    ix = ix-1;
    PageTable[ix] = ix*514+nx; // ix counts down -1 for every two bytes,
      // so the page # is actually ix*2 on the left (=ix*2*256), plus ix*2+1 on the right;
      // the +1 part is built into nx (=0xC0C1). nx also adds on an offset to start the table
      // in the top quadrant (+0x40) and a write enable bit (0x80). Later, when we reach
      // the middle (ix==16), we remove the +0x40 offset for the final (lower) quadrant.
    if (ix>23)
      AppPIB[ix+8] = ix*514+0xA0A1; // user MMU will be middle 64K
    else AppPIB[ix+8] = 126*257; // except stack is mostly disabled
      // the user MMU table is embedded in the same array as the PIB, offset +16 bytes.
      // each page is offset +32 to get the middle two quadrants of memory
    if (ix==16) nx = 0x8081;} //
  AppPIB[8] = 0x80A1; // user PIB is in page 0 = real page 0
    // Note that page 1 is still offset +32 (i.e. at hex 0xA1)
  fi = Open(prog);
  nx = Read(fi,DiskBlk); // read the whole program if < 2K
  Close(fi);
  ix = DiskBlk[0];
  PrintStr("Got "); PrintNum(ix); PrintStr(" bytes...");
  if (nx<ix || ix<30) {
    if (nx<ix) PrintStr("Program is too big");
      else PrintStr("Could not open code file");
    return;}
  PageTable[31] = 0xDFFF; // point my page 126 to user page 63
    // Page 63 is still the (writeable) I/O page, =255 =0xFF, we only change page 62
    // (the left byte) to be user page 63, which is actual page 95, bits: W101.1111, =0xDF
  nx = SysOp1(17, &PageTable); // tell system to turn MMU on
  nx = maxCode;
  ix = 0xFC00;
  while (nx>0) {
    nx = nx-1;
    ix = ix-2;
    mmPOKE(ix, DiskBlk[nx]); // mmPOKE here, so the MMU page table is used
      // actually, it's used anyway, because this executes in user mode
    if ((nx & 511)==0) {
      PageTable[31] = PageTable[31] - 0x100; // move it down 1 page
        // subtract 1 from the left byte, which is the mobile page, now = user 62
      ix = 0xFC00;}}
  CodeBase = 2-maxCode*2; // nominal start of code
  Stack = 1922; // start stack in page 1 (1026)
  ix = Stack-3072; // now points to user page 1 (still my page 126)
  PageTable[31] = 0xA1FF; // my page 126 now points to user page 1
  POKE(ix-2, 0x2009); // SysCall(0) for quit
  POKE(ix+2, Stack-2); // return address is that SysCall
  POKE(ix, Stack); // globals ptr, =FP
  POKE(ix+4, CodeBase);
  POKE(ix+6, Stack);  // initial FP
  POKE(ix+8, DiskBlk[1]+CodeBase); // initial PC
  AppPIB[3] = Stack+8; // initial SP, where Process Mgr can find it
  AppPIB[2] = 16+&AppPIB; // user MMU, so Process Mgr can find it
  AppPIB[0] = ReadyList; // link to front of ReadyList
  ReadyList = &AppPIB;
  PrintStr(" Success!\r");} // ~LoadProgram

// Start new thread...
void TakenShow() { // a thread to take chars from queue & display them
  int temp;   // maps to DemoFrame[7] (allow 4 ints for PIB)
  DemoFrame[10] = &DemoFrame[13]; // initial SP
  DemoFrame[12] = &DemoFrame[4];  // initial FP
  DemoFrame[4] = PEEK((&temp)-6);  // copy my globals ptr
  DemoFrame[5] = 0;        // halt if it returns (it shouldn't)
  temp = FORK(&DemoFrame[10]);  // returns twice:
  if (temp==0) return; // returned in caller's process
  // otherwise returned in new process (first)..
  POKE(temp-6,0);  // clear caller's return value
  POKE(CurProcess+6,temp); // save caller's SP in its PIB
  POKE(CurProcess,ReadyList); // move caller to ready list..
  ReadyList = CurProcess;
  CurProcess = &DemoFrame; // make me current
  while (true) { // OK, process the data..
    Wait(take);
    PrintChar(queue[tx]);
    Signal(fill);
    tx = (tx+1)%Qsize;}
  } // ~TakenShow

void DoSemDemo() { // Demonstrate Semaphores..
  int ix = Qsize;
  char[1] GetKey;
  while (ix>0) { // initialize this semaphore; take is already OK
    Signal(fill);
    ix = ix-1;}
  TakenShow(); // go fork off a thread to do the other end
  while (true) { // OK, process the data..
    Wait(fill);
    ix = Read(Kin, GetKey); // get a keystroke
    ix = ORD(GetKey[0]);
    queue[fx] = CHR(ix^((ix>>1)&32)); // [capitalize letters]
    Signal(take);
    fx = (fx+1)%Qsize;}
  } // ~DoSemDemo

// The main program..
void main() {
  int here = 0;
  String str, prog;
  MiniOS(); // start up operating system
  while (true) {
    here = 0;
    PrintStr("\rQuit, Copy (Flop->HD), <filename>\r> ");
    str = ReadStr();
    if (StrEqual(str,"Quit")) break;
    else if (StrEqual(str,"Demo")) DoSemDemo();
    while (true) {
      prog = GetWord(str,here);
      here = here+1;
      if (LENGTH(prog)==0) break;
      if (StrEqual(prog,"Copy")) CopyFlop();
      else if (StrEqual(prog,"Dir")) ShowDir();
      else {
        LoadProgram(prog); // load it and start it up as a process
        if (SysOp0(13)==0) ;}}} // wait for termination
  PrintStr("\rBye!");} // ~main