Computer Science 3 - 2015

Programming Language Translation


Practical for Week 2, beginning 7 September 2015 - Solutions

There were some very good solutions submitted, and some energetic ones too - clearly a lot of students had put in many hours developing their code. This is very encouraging, but there was also evidence of "sharing" out the tasks, not really working together a proper group, and not developing an interpreter that was up to the later tasks. And do learn to put your names into the introductory comments of programs that you write.

Full source for the solutions summarized here can be found in the ZIP file on the servers - PRAC2A.ZIP

Task 3 involved reading some Parva code for a simple algorithm and then adding suitable commentary. It is highly recommended that you adopt the style shown below, where the higher level code acts as commentary, rather than adopting a line by line explanation of each mnemonic/opcode.

; Read a zero-terminated list of numbers and write it backwards (say what it does)
; P.D. Terry, Rhodes University, 2015                           (and who was responsible)

    0 DSP      3   0 list, 1 i, 2 n      42 LDA      1
    2 LDA      0                         44 LDA      1
    4 LDC      10  const max = 10;       46 LDV
    6 ANEW         int[] list =          47 LDC      1
    7 STO            new int [max];      49 ADD
    8 LDA      1                         50 STO            i = i + 1;
   10 LDC      0                         51 LDA      2
   12 STO           i = 0;               53 INPI           read(n);
   13 LDA      2                         54 BRN      16  } // while
   15 INPI          read(n);             56 LDA      1
   16 LDA      2                         58 LDV
   18 LDV                                59 LDC      0
   19 LDC      0                         61 CGT          while (i > 0) {
   21 CNE           while (n ! 0         62 BZE      84
   22 LDA      1                         64 LDA      1
   24 LDV                                66 LDA      1
   25 LDC      10     // max             68 LDV
   27 CLT                                69 LDC      1
   28 AND             && i < max) {      71 SUB
   29 BZE      56                        72 STO            i = i - 1;
   31 LDA      0                         73 LDA      0
   33 LDV                                75 LDV
   34 LDA      1                         76 LDA      1
   36 LDV                                78 LDV
   37 LDXA                               79 LDXA
   38 LDA      2                         80 LDV
   40 LDV                                81 PRNI           write(list[i]);
   41 STO             list[i] = n;       82 BRN      56  } // while
                                         84 HALT

It is easy to see that this does not use short circuit evaluation of Boolean expressions, as it uses AND, which is an infix operator that requires its two operands both to have been evaluated and pushed onto the expression stack. However, it is easy to eliminate the AND (and, in the code on the right, even a comparison)"

   16 LDA      2                         16 LDA      2                        16 LDA      2
   18 LDV                                18 LDV                               18 LDV
   19 LDC      0                         19 LDC      0                        19 BZE      54
   21 CNE           while (n != 0        21 CNE           while (n != 0       21 LDA      1    while (n != 0
   22 LDA      1                         22 BZE      57                       22 LDV
   24 LDV                                24 LDA      1                        24 LDC      10     // max
   25 LDC      10     // max             26 LDV             // max            26 CLT             && i < max)
   27 CLT                                27 LDC      10                       27 BZE      54
   28 AND             && i < max) {      29 CLT             && i < max) {     29 LDA      0
   29 BZE      56                        30 BZE      57                          ...
   31 LDA      0                         32 LDA      0
      ...                                   ...

(Other examples of short circuit coding are to be found in the solution to this week's test.)


Task 4 - Execution overheads - part one

See discussion of Task 9 below.


Task 5 - Palindromic sequences

Task 5 was to hand-compile the numerical palindrome checking into PVM code. Most people got a long way towards this.

Again, have a look at how I have commented this, using "high level" code, rather than detailed line by line commentary of the form "load address of X". Many of the submissions had "commentary" that was, frankly, almost useless. Try the following test for assembler code: Cover over the real code with a piece of paper and read only the comments. Does what you read make sense on its own? I maintain that it should. The easiest way to do this is by using a high level algorithmic notation.

    ; Read a sequence of numbers and report           66 SUB
    ; whether they form a palindromic                 67 STO           high = n - 1;
    ; sequence (reads the same from either end)       68 LDA      1    // start while test
    ; examples:  1 2 3 4 3 2 1  is palindromic        70 LDV
    ;            1 2 3 4 4 3 2  is non-palindromic    71 LDA      0
    ; P.D. Terry, Rhodes University, 2015             73 LDV
    0 DSP      6                                      74 LDC      1
    2 LDA      5                                      76 SUB
    4 LDC      100                                    77 CLT
    6 ANEW                                            78 BZE      124  while (low < n - 1) {
    7 STO           int [] list = new int [10];       80 LDA      5
    8 LDA      0                                      82 LDV
   10 LDC      0                                      83 LDA      1
   12 STO           n = 0                             85 LDV
   13 LDA      3                                      86 LDXA
   15 INPI          read(item)                        87 LDV             // list[low]
   16 LDA      3                                      88 LDA      5
   18 LDV                                             90 LDV
   19 LDC      0                                      91 LDA      2
   21 CNE                                             93 LDV
   22 BZE      49   while (item != 0) {               94 LDXA
   24 LDA      5                                      95 LDV             // list[high[
   26 LDV                                             96 CNE
   27 LDA      0                                      97 BZE      104    if (list[low] != list[high])
   29 LDV                                             99 LDA      4
   30 LDXA                                           101 LDC      0
   31 LDA      3                                     103 STO               isPalindrome = false;
   33 LDV                                            104 LDA      1
   34 STO             list[n] = item;                106 LDA      1
   35 LDA      0                                     108 LDV
   37 LDA      0                                     109 LDC      1
   39 LDV                                            111 ADD
   40 LDC      1                                     112 STO             low = low + 1;
   42 ADD                                            113 LDA      2
   43 STO             n = n + 1;                     115 LDA      2
   44 LDA      3                                     117 LDV
   46 INPI            read(item);                    118 LDC      1
   47 BRN      16   } // while                       120 SUB
   49 LDA      4                                     121 STO             high = high - 1;
   51 LDC      1                                     122 BRN      68   } // while
   53 STO           isPalindrome = true;             124 LDA      4
   54 LDA      1                                     126 LDV
   56 LDC      0                                     127 BZE      133  if (isPalindrome)
   58 STO           low = 0;                         129 PRNS     "Palindromic sequence"
   59 LDA      2                                     131 BRN      135  else
   61 LDA      0                                     133 PRNS     "Non-palindromic sequence"
   63 LDV                                            135 HALT
   64 LDC      1


Task 6 - Trapping overflow and other pitfalls

Checking for overflow in multiplication and division was not always well done. You cannot multiply and then try to check overflow (it is too late by then) - you have to detect it in a more subtle way. Here is one way of doing it - note the check to prevent a division by zero. This does not use any precision greater than that of the simulated machine itself. Note that it is necessary to check for "division by zero" in the rem code as well!

       case PVM.mul:           // integer multiplication
         tos = Pop();
         sos = Pop();
         if (tos != 0 && Math.Abs(sos) > maxInt / Math.Abs(tos)) ps = badVal;
         else Push(sos * tos);
         break;
       case PVM.div:           // integer division (quotient)
         tos = Pop();
         if (tos == 0) ps = divZero;
         else Push(Pop() / tos);
         break;
       case PVM.rem:           // integer division (remainder)
         tos = Pop();
         if (tos == 0) ps = divZero;
         else Push(Pop() % tos);
         break;

or for the "inline" assembler

       case PVM.mul:           // integer multiplication
         tos = mem[cpu.sp++];
         if (tos != 0 && Math.Abs(mem[cpu.sp]) > maxInt / Math.Abs(tos)) ps = badVal;
         else mem[cpu.sp] *= tos;
         break;
       case PVM.div:           // integer division (quotient)
         tos = mem[cpu.sp++];
         if (tos != 0) mem[cpu.sp] /= tos;
         else ps = divZero;
         break;
       case PVM.rem:           // integer division (remainder)
         tos = mem[cpu.sp++];
         if (tos != 0) mem[cpu.sp] %= tos;
         else ps = divZero;
         break;

It is possible to use an intermediate long variable (but don't forget the casting operations or the Abs function):

       case PVM.mul:           // integer multiplication
         tos = Pop();
         sos = Pop();
         long temp = (long) sos * (long) tos;
         if (Math.Abs(temp) > maxInt) ps = badVal;
         else Push(sos * tos);
         break;

The palindrome checker program, when given too long a sequence of non-zero numbers for the array to handle, terminated with an array bounds error corectly trapped by the Push/Pop assembler. The same error was not trapped by the Inline system, which gaily allows the LDXA opcode to wander wheresoever it likes. To fix this resuires the following changes to the PVM Inline interpreter. This strategy is discussed in the textbook!

       case PVM.anew:          // heap array allocation
         int size = mem[cpu.sp];
         if (size <= 0 || size + 1 > cpu.sp - cpu.hp - 2)
           ps = badAll;
         else {
           mem[cpu.hp] = size;
           mem[cpu.sp] = cpu.hp;
           cpu.hp += size + 1;
         }
         break;

       case PVM.ldxa:          // heap array indexing       .
         int adr = mem[cpu.sp++];
         int heapPtr = mem[cpu.sp];
         if (heapPtr == 0) ps = nullRef;
         else if (heapPtr < heapBase || heapPtr >= cpu.hp) ps = badMem;
         else if (adr < 0 || adr >= mem[heapPtr]) ps = badInd;
         else mem[cpu.sp] = heapPtr + adr + 1;
         break;


Task 6 - Your lecturer is quite a character

To be able to deal with input and output of character data we need to add two new opcodes, modelled on the INPI and PRNI codes whose interpretation would be as below. All of the new opcodes require additions to the lists of opcodes in the assembler and interpreter (be careful of two word opcodes that are mentioned in several places).

       case PVM.inpc:          // character input
         adr = Pop();
         if (InBounds(adr)) {
           mem[adr] = data.ReadChar();
           if (data.error()) ps = badData;
         }
         break;
       case PVM.prnc:          // character output
         if (tracing) results.write(padding);
         results.Write((char) (Math.Abs(Pop()) % (maxChar + 1)), 1);
         if (tracing) results.WriteLine();
         break;

or for the "inline" assembler

       case PVM.inpc:          // character input
         mem[mem[cpu.sp++]] = data.ReadChar();
         break;
       case PVM.prnc:          // character output
           if (tracing) results.Write(padding);
         results.Write((char) (Math.Abs(mem[cpu.sp++]) % (maxChar + 1)), 1);
           if (tracing) results.WriteLine();
         break;

Note that the PRNC opcode outputs the character in a field width of 1, not 0 as most people tried. This has the effect that we can output characters without intervening spaces. Note also the way in which the value is forced "modulo 256" to become a valid ASCII value. I don't recall seeing anyone do this.

To build a really safe system there are further refinements we should make. It can be argued that we should not try to store a value outside ot the range 0 .. 255 into a character variable. This suggests that we should have a range of STO type instructions that check the value on the top of stack before assigning it. One of these - STOC to act as a variation on STO - would be interpreted as follows; we would need others to handle STLC, STLC_0 and so on (these have not yet been implemented in the solution kit).

       case PVM.stoc:          // character checked store
         tos = Pop(); adr = Pop();
         if (inBounds(adr))
           if (tos >= 0 && tos <= maxChar) mem[adr] = tos; else ps = badVal;
         break;

or for the "inline" assembler

       case PVM.stoc:          // character checked store
         tos = mem[cpu.sp++]; mem[mem[cpu.sp++]] = tos;
         break;

Introducing opcodes to convert to lower case and check for a letter is simply done by using the methods from the C# Char wrapper class (notice the need for casting operations as well, to satisfy the C# compiler):

       case PVM.low:           // toLowerCase
         Push(Char.ToLower((char) Pop()));
         break;
       case PVM.islet:         // isLetter
         tos = Pop();
         Push(Char.IsLetter((char) tos) ? 1 : 0);
         break;


or for the "inline" assembler

       case PVM.low:           // toLowerCase
         mem[cpu.sp] = Char.ToLower((char) mem[cpu.sp]);
         break;
       case PVM.islet:         // isLetter
         mem[cpu.sp] = Char.IsLetter((char) mem[cpu.sp]) ? 1 : 0;
         break;

As an example of using the new input/output opcodes, here is the encryption program. Notice that we have had to hard-code 46 as the integer equivalent of character '.', of course, and similarly hard-coded 97 as the integer equivalent of 'a'.

; rot13 encryption of a text terminated with a period     25  LDC    97 ;
; P.D. Terry, Rhodes University, 2015                     27  SUB       ;
   0  DSP    1  ; ch at 0                                 28  LDC    13 ;
   2  LDA    0  ; repeat {                                30  ADD       ;
   4  INPC      ;   read(ch);                             31  LDC    26 ;
   5  LDA    0  ;                                         33  REM       ;
   7  LDA    0  ;                                         34  ADD       ;
   9  LDV       ;                                         35  STOC      ;      ch = 'a' + (ch-'a'+13) % 26;
  10  LOW       ;                                         36  LDA    0  ;
  11  STOC      ;   ch = lowercase(ch);                   38  LDV       ;
  12  LDA    0  ;                                         39  PRNC      ;   write(ch)
  14  LDV       ;                                         40  LDA    0  ;
  15  ISLET     ;                                         42  LDV       ;
  16  BZE    36 ;   if (isletter(ch))                     43  LDC    46 ;
  18  LDA    0  ;                                         45  CEQ       ;
  20  LDC    97 ;                                         46  BZE    2  ; } until (ch == '.');
  22  LDA    0  ;                                         48  HALT      ; System.Exit(0);


Task 8 - Improving the opcode set

This is straightforward, if a little tedious, and it is easy to leave some of the changes out and get a corrupted solution. The PVMAsm class requires modification in the switch statement that recognizes two-word opcodes:

   case PVM.brn:                            // all require numeric address field
   ...
   case PVM.ldc:
   case PVM.ldl:  // +++++++++++++++++  addition
   case PVM.stl:  // +++++++++++++++++  addition
     codeLen = (codeLen + 1) % PVM.memSize;
     if (ch == '\n')                        // no field could be found
       error("Missing address", codeLen);
     else {                                 // unpack it and store
       PVM.mem[codeLen] = src.readInt();
       if (src.error()) error("Bad address", codeLen);
     }
     break;

The PVM class requires several additions.

We must add to the switch statement in the trace and listCode methods (several submissions missed this):

    static void trace(OutFile results, int pcNow) {
      switch (cpu.ir) {
        ...
        case PVM.ldl:  // +++++++++++++++++  addition
        case PVM.stl:  // +++++++++++++++++  addition
      }
      results.writeLine();
    }

and we must provide case arms for all the new opcodes. A selection of these follows; the rest can be seen in the solution kit. Notice that for consistency all the "inBounds" checks should be performed on the new opcodes too (several submissions missed this).

       case PVM.ldc_0:         // push constant 0
         Push(0);
         break;
       case PVM.ldc_1:         // push constant 1
         Push(1);
         break;
       ...
       case PVM.lda_0:         // push local address 0
         adr = cpu.fp - 1;
         if (inBounds(adr)) Push(adr);
         break;
       case PVM.lda_1:         // push local address 1
         adr = cpu.fp - 2;
         if (inBounds(adr)) Push(adr);
         break;
       ...
       case PVM.ldl:           // push local value
         adr = cpu.fp - 1 - next();
         if (inBounds(adr)) Push(mem[adr]);
         break;
       case PVM.ldl_0:         // push value of local variable 0
         adr = cpu.fp - 1;
         if (inBounds(adr)) Push(mem[adr]);
         break;
       case PVM.ldl_1:         // push value of local variable 1
         adr = cpu.fp - 2;
         if (inBounds(adr)) Push(mem[adr]);
         break;
       ...
       case PVM.stl:           // store local value
         adr = cpu.fp - 1 - next();
         if (inBounds(adr)) mem[adr] = Pop();
         break;
       case PVM.stlc:          // character checked pop to local variable
         tos = Pop(); adr = cpu.fp - 1 - Next();
         if (InBounds(adr))
           if (tos >= 0 && tos <= maxChar) mem[adr] = tos;
           else ps = badVal;
         break;
       case PVM.stl_0:         // pop to local variable 0
         adr = cpu.fp - 1;
         if (inBounds(adr)) mem[adr] = Pop();
         break;
       case PVM.stl_1:         // pop to local variable 1
         adr = cpu.fp - 2;
         if (inBounds(adr)) mem[adr] = Pop();
         break;
       ...

or for the "inline" assembler

       case PVM.ldc_0:         // push constant 0
         mem[--cpu.sp] = 0;
         break;
       case PVM.ldc_1:         // push constant 1
         mem[--cpu.sp] = 1;
         break;
       ...
       case PVM.lda_0:         // push local address 0
         mem[--cpu.sp] = cpu.fp - 1;
         break;
       case PVM.lda_1:         // push local address 1
         mem[--cpu.sp] = cpu.fp - 2;
         break;
       ...
       case PVM.ldl:           // push local value
         mem[--cpu.sp] = mem[cpu.fp - 1 - mem[cpu.pc++]];
         break;
       case PVM.ldl_0:         // push value of local variable 0
         mem[--cpu.sp] = mem[cpu.fp - 1];
         break;
       case PVM.ldl_1:         // push value of local variable 1
         mem[--cpu.sp] = mem[cpu.fp - 2];
         break;
       ...
       case PVM.stl:           // store local value
         mem[cpu.fp - 1 - mem[cpu.pc++]] = mem[cpu.sp++];
         break;
       case PVM.stlc:          // store local value
         mem[cpu.fp - 1 - mem[cpu.pc++]] = mem[cpu.sp++];
         break;
       case PVM.stl_0:         // pop to local variable 0
         mem[cpu.fp - 1] = mem[cpu.sp++];
         break;
       case PVM.stl_1:         // pop to local variable 1
         mem[cpu.fp - 2] = mem[cpu.sp++];
         break;
       ...

We must add to the method that lists out the code (several submissions may have missed this). :

    public static void listCode(String fileName, int codeLen) {
        ...
       case PVM.brn:
       case PVM.ldc:
       case PVM.ldl: // +++++++++++++++++  addition
       case PVM.stl: // +++++++++++++++++  addition
         i = (i + 1) % memSize; codeFile.write(mem[i]);
         break;

The INC and DEC operations are best performed by introducing opcodes that assume that an address has been planted on the top of stack for the variable (or array element) that needs to be incremented or decremented. This may not have been apparent to everyone.

       case PVM.inc:           // ++
         adr = Pop();
         if (inBounds(adr)) mem[adr]++;
         break;
       case PVM.dec:           // --
         adr = Pop();
         if (inBounds(adr)) mem[adr]--;
         break;


or for the "inline" assembler

       case PVM.inc:           // ++
         mem[mem[cpu.sp++]]++;
         break;
       case PVM.dec:           // --
         mem[mem[cpu.sp++]]--;
         break;

Finally we must add to the section that initializes the mnemonic lookup table:

    public static void init() {
      ...
      mnemonics[PVM.ldl]    = "LDL";    // +++++++++++++++++  additions
      mnemonics[PVM.stl]    = "STL";
      mnemonics[PVM.lda_0]  = "LDA_0";
      ...

Here are the encoding program and the character palindrome programs recoded using these new opcodes.

The palindrome program has also been optimised so as to terminate the checking loop as quickly as possible:

; Read a sequence of characters terminated by a period and report whether
; they form a palindrome (one that reads the same from each end)
                                                         40  STL_1          low = 0;
; Examples:   too hot to hoot.  is palindromic           41  LDL_0
;             1234432.          is non-palindromic       42  LDC_1
; P.D. Terry, Rhodes University, 2015                    43  SUB
                                                         44  STL_2          high = n -1;
    0  DSP      6                                        45  LDL      4
    2  LDC      100                                      47  NEG
    4  ANEW                                              48  BZE      77    while (isPalindrome
    5  STL      5     char [] str = new char [100];      50  LDL_1
    7  LDC_0                                             51  LDL_2
    8  STL_0          n = 0;                             52  CLT
    9  LDA_3                                             53  BZE      77      && low < high) {
   10  INPC           read(ch)                           55  LDL      5
   11  LDL_3                                             57  LDL_1
   12  LDC      46                                       58  LDXA
   14  CNE                                               59  LDV                 // str[low]
   15  BZE      36    while (ch != ',') {                60  LDL      5
   17  LDL_3                                             62  LDL_2
   18  LDC      32                                       63  LDXA
   20  CGT                                               64  LDV                 // str[high]
   21  BZE      32      if (ch > ' ') {                  65  CNE              if (str[low] != str[high])
   23  LDL      5                                        66  BZE      71
   25  LDL_0                                             68  LDC_0              isPalindrome = false;
   26  LDXA                                              69  STL      4
   27  LDL_3                                             71  LDA_1
   28  LOW                                               72  INC              low++;
   29  STOC               str[n] = lower(ch);            73  LDA_2
   30  LDA_0                                             74  DEC              high--;
   31  INC                n++;                           75  BRN      45    } // while
   32  LDA_3            }                                77  LDL      4
   33  INPC             read(ch);                        79  BZE      85    if (isPalindrome)
   34  BRN      11    } // while                         81  PRNS     "Palindromic string"
   36  LDC_1                                             83  BRN      87    else
   37  STL      4     isPalinDrome = true;               85  PRNS     "Non-palindromic string"
   39  LDC_0                                             87  HALT

The encoding program has been optimized in several respects - can you see them all?

; rot13 encryption of a text terminated with a period
; P.D. Terry, Rhodes University, 2015                    17  SUB
   0  DSP    1  ; ch at 0                                18  LDC    26
   2  LDA_0     ; repeat {                               20  REM
   3  INPC      ;   read(ch);                            21  ADD
   4  LDL_0                                              22  I2C
   5  LOW                                                23  STLC   0  ;    ch = (char) ('a' + (ch-'a'+13) % 26);
   6  STLC   0  ;   ch = lowercase(ch);                  25  LDL_0
   8  LDL_0                                              26  PRNC      ;   write(ch)
   9  ISLET                                              27  LDL_0
  10  BZE    25 ;   if (isletter(ch))                    28  LDC    46
  12  LDC    97        // 'a'                            30  CEQ
  14  LDL_0                                              31  BZE    2  ; } until (ch == '.');
  15  LDC    84 ;      // 'a' - 13                       33  HALT      ; System.Exit(0);


Task 9 - Execution overheads - part two

In the prac kit you were supplied with a second translation SIEVE2.PVM of a cut down version of the same prime-counting program SIEVE.PAV as was used in Task 4, but this time using the extended opcode set developed in the last task.

Running SIEVE1.PVM through both of the original and modified assemblers, and SIEVE2.PVM through both of the modified assemblers gave the following timings for the same limit (4000) and number of iterations (100) on my machines, one a laptop running Windows XP and one a desktop running Windows 7-32.

   ┌────────────────────────────────┬────────────────────────┬────────────────────────┐
   │ Desktop Machine (Win 7-32)     │  Sieve1.pvm            │  Sieve2.pvm            │
   │                                │                        │                        │
   │ ASM1  (Push/Pop - original)    │      0.73              │       -                │
   │ ASM2  (Inline - original)      │      0.30     (0.41)   │       -                │
   │                                │                        │                        │
   │ ASM3  (Push/Pop - extended)    │      0.72              │      0.55              │
   │ ASM4  (Inline - extended)      │      0.33     (0.45)   │      0.15      (0.27)  │
   └────────────────────────────────┴────────────────────────┴────────────────────────┘


   ┌────────────────────────────────┬────────────────────────┬────────────────────────┐
   │ Laptop machine (XP-32)         │  Sieve1.pvm            │  Sieve2.pvm            │
   │                                │                        │                        │
   │ ASM1  (Push/Pop - original)    │      1.34              │      -                 │
   │ ASM2  (Inline - original)      │      0.51     (0.38)   │      -                 │
   │                                │                        │                        │
   │ ASM3  (Push/Pop - extended)    │      1.16              │      0.86              │
   │ ASM4  (Inline - extended)      │      0.51     (0.45)   │      0.26      (0.30)  │
   └────────────────────────────────┴────────────────────────┴────────────────────────┘

The Desktop times were about 55-65% of those on the Laptop

The Inline times were between 38-45% of the Push/Pop system with the original limited opcode set.

The Inline times were between 27-30% of the Push/Pop system with the extended opcode set,

If one wishes to improve the performance of the interpreter further it might make sense to get some idea of which opcodes are executed most often. Clearly this will depend on the application, and so a mix of applications might need to be analysed. It is not difficult to add a profiling facility to the interpreter, and this has been done in yet another interpreter that you can find in the solution kit. Running this on the Sieve files yielded some interesting results. For a start, there were enormous numbers of steps executed - probably more than you might have thought.

         550 primes                 550 primes

        Original opcodes           Extended opcode set

        39 494 323 operations.     27 070 118 operations.  (68%)


        HALT           1           INC       799900
        ANEW           1           LDC_2        200
        BZE      2182801           LDC_1     454902
        BRN      1727700           LDC_0    1910701
        CGT       982800           STL_3          1
        CLE      1782901           STL_2     982800
        AND       982800           STL_1        200
        ADD      1782701           STL_0          1
        PRNS           1           LDL_3        101
        PRNI           1           LDL_2    3821200
        LDXA     1727600           LDL_1    2582600
        STO      3165703           LDL_0    1727600
        LDV      9386302           LDA_3        100
        LDA     10824405           LDA_1     799800
        LDC      4948605           STL        55101
        DSP            1           LDL        55001
                                   HALT           1
                                   ANEW           1
                                   BZE      2182801
                                   BRN      1727700
                                   CGT       982800
                                   CLE      1782901
                                   AND       982800
                                   ADD       982801
                                   PRNS           1
                                   PRNI           1
                                   LDXA     1727600
                                   STO      1327700
                                   LDV       399900
                                   LDC      1782902
                                   DSP            1


Home  © P.D. Terry