Updated 2012-10-03 06:05:09 by jdc

My first computer was an Olivetti Programma 101 owned by my high school, and I spent many hours learning how to program using it. As chance would have it, it was also the first computer my wife worked with as well - though she only got a few hours. At any rate, I wrote this for my wife's birthday. Those of you who have also used this machine will recognize it immediately.

This program is not typical Tcl. Because our current computers have quite different screen resolutions, I needed to make it resizable, at least to some extent. So I came up with a system to generate a display from a bitmap, and widgets are located by decimal places - .5 would be 50% of the way from the left side, and so on. It also extracts the actual button label from that portion of the image, so I don't need to specify it separately.

This program has been lightly tested, but there are probably still bugs. The worst I've dealt with are actually edit errors that moved widgets to West Succotash, Iowa or someplace because I dinged the table of widget placement data. Hopefully, this version straightens out all of that. It should also be noted there are quite a few new features added to the architecture to make it a little less painful to program, including editing of programs (if you made a mistake in entering the current instruction you could CLEAR and re-enter it, but if you don't notice the error right away and spot it scrolling up while you are entering a different command, well, you lose and you have to re-enter the whole program all over again). Also, the blue buttons represent various extended functions allowing you to display the registers, edit the labels, and so on, and I also added the "rub out" button and the keyboard buffer display. You just had to remember what digits you keyed in with the actual hardware.

Aside from these additions the layout exactly follows the original except for moving the decimal wheel from the right to the left.

The variable "runfrom" can be set to "." or to "p101.exe" according to whether it is run as a script or as a tclkit. In either case, it needs a companion "images" directory with the following contents:

The program is below, but it needs a variety of images and so forth. I tried to upload those and got complaints about an image format not being text/wikit format. It would be easiest to just upload the p101.vfs as a zip file. If someone can help me circumvent these issues?

CMcC Could you please elaborate on "No .exe, no .zip, and no .gif either since I somehow changed the type of the file, whatever THAT means" ?

The wiki refuses to upload .exe and .zip files. Likely this is not a bug but a security feature, either one may host malware. Okay, re-reading the image upload and doing it the correct way seems to work - although I find the method counter-intuitive in the extreme - but when I uploaded a .ico file it was still assigned a page number and the .htm extension, and when I did "save Link" and renamed it back to .ico, it didn't work. Likely not 8-bit clean somewhere in the loop.

Now, after some thought, I expect this is because it is assuming that the upload file replaces the original wiki page, and it won't do that. Again, probably a a feature rather than a bug. It leaves me not knowing how to upload an image as such under its own name and type.

Secondly, consider using the Half Bakery for uploading your code. I think the basic problem here is that you're uploading a package which could be modelled as a collection containing several components, and wiki generally doesn't really lend itself to a hierarchy by inclusion.

I hadn't thought of the Half Bakery as a release mechanism. I'll look into that. <sigh>I don't see how to upload here, either. Uploading a .zip or a .tgz add the archive name preceded by an 'x' link, but it doesn't seem to have actually added it to the repository and pressing the x just seems to delete it.</sigh> I really agree with uniquename, a simply upload button would require far less explanation or documentation. It reminds me of the whole "how come Tcl barfs when it sees 09?" controversy.
package provide app-p101 1.0
# set runfrom p101.exe
set runfrom .

proc setfonts { brw brh bufw dispch spfnsz brfnsz \
                txtfnsz HLfnsz abtfnsz regfnsz lblfnsz \
} {
  set ::brheight $brh
  set ::brwidth $brw
  set ::bufwidth $bufw
  set ::dispch $dispch
  set ::spfn [ font create -family courier -weight bold -size $spfnsz ]
  set ::brfn [ font create -family courier -weight bold -size $brfnsz ]
  set ::txtfn [ font create -family courier -weight bold -size $txtfnsz ]
  set ::HLfn [ font create -family tahoma -weight bold -size $HLfnsz ]
  set ::abtfn [ font create -family tahoma -weight normal -size $abtfnsz ]
  set ::regfn [ font create -family tahoma -size $regfnsz ]
  set ::lblfn [ font create -family tahoma -weight normal -size $lblfnsz ]
}

proc log { msg } {
  if !$::uselog return
  if !$::logopen {
    if { [ catch {
      set ::logfile [ open ./logfile.txt w ]
      set ::logopen 1
    } error ] != 0 } {
      set ::uselog 0
    }
  }
  puts $::logfile $msg
  flush $::logfile
}

proc announce { msg } {
  tk_messageBox -parent .top -title "Info" \
    -icon info -type ok -message $msg
}

proc reset { { override 0 } } {
  if !$override {
    if {[tk_messageBox -parent . -title "Are you sure?" \
           -icon question -type yesno -default no \
           -message "Reset P101 Simulator?"] ne "yes"
    } {
      return
    }
  }
  .top.printarea.text delete 0.0 end
  .prog.list.list delete 0 end
  .prog.list.list insert end "end" ; renum .prog.list
  set ::pc 0
  set ::reg ""
  set ::confirm 0
  set ::entering 0
  set ::recording 0
  set ::interactive 1
  set ::register(A) 0.0
  set ::register(B) 0.0
  set ::register(b) 0.0
  set ::register(C) 0.0
  set ::register(c) 0.0
  set ::register(D) 0.0
  set ::register(d) 0.0
  set ::register(E) 0.0
  set ::register(e) 0.0
  set ::register(F) 0.0
  set ::register(f) 0.0
  set ::register(R) 0.0
  set ::register(M) 0.0
  set stack {}
  set regstack {}
  .top.red configure -bg $::dkred
  foreach w $::btnlist { $w configure -state normal }
  .top.face delete vwyzlbl
  updlbls
}

proc down { reg } {
  set ::register(A) $::register($reg)
  interactive $::register($reg) $reg \u2193
}

proc up { reg } {
  set ::register($reg) $::register(M)
  interactive $::register($reg) $reg \u2191
}

proc swap { reg } {
  if { $reg eq "/" } {
      set A $::register(A)
      set ::register(M) [= {$A-entier($A)} ]
      interactive $::register(M) / \u2195
    return
  }
  if { $reg eq "A" } { 
    set ::register(A) [= {abs($::register(A))}]
  } else {
    set temp $::register(A)
    set ::register(A) $::register($reg)
    set ::register($reg) $temp
  }
  interactive $::register($reg) $reg \u2195
}

proc sqrt { reg } {
  set ::register(A) [= {sqrt($::register($reg))}]
  set ::register(M) $::register($reg)
  interactive $::register(A) $reg \u221a
}

proc minus { reg } {
  set ::register(A) [= {$::register(A) - $::register($reg)}]
  set ::register(M) $::register($reg)
  interactive $::register(A) $reg -
}

proc times { reg } {
  set ::register(A) [= {$::register(A) * $::register($reg)}]
  set ::register(M) $::register($reg)
  interactive $::register(A) $reg \u00d7
}

proc plus { reg } {
  set ::register(A) [= {$::register(A) + $::register($reg)}]
  set ::register(M) $::register($reg)
  interactive $::register(A) $reg +
}

proc divide { reg } {
  set A [= {entier($::register(A))}]
  if {[ string first "." $::register(A) ] == -1 } {
    set ::register(A) $::register(A).0
  }
  if { 0 != [ catch {
    set ::register(A) [= {$::register(A) / $::register($reg)}]
    set ::register(R) 0
    set R [= {entier($::register($reg))}]
    set ::register(R) [= {$A % $R}]
    set ::register(M) $::register($reg)
    interactive $::register(A) $reg \u00f7
  } ] } { redlight -1.0 }
}

proc zero { reg } {
  set ::register($reg) 0
  interactive $::register($reg) ${reg} *
}

proc prreg { reg } {
  type $::register($reg) ${reg}\u25c7
}

proc renum { w } {
  $w.linecount delete 0 end
  set maxj [ $w.list index end ]
  for { set j 0 } { $j < $maxj } { incr j } {
    $w.linecount insert end $j
  }
}

proc insins { instr } {
  .prog.list.list insert $::pc "$instr"
  type "$::pc: $instr"
  renum .prog.list
}

proc delins { } {
  if { [ .prog.list.list get $::pc ] eq "end" } return
  .prog.list.list delete $::pc $::pc
  renum .prog.list
}

proc clear { } {
  if $::recording {
    delins
    return
  }
  .top.red configure -bg $::dkred
  .top.buffer.entry delete 0 end
  foreach w $::btnlist { $w configure -state normal }
}

proc recprog { } {
  if $::recording {
    set ::recording 0
    .top.recprog configure -relief raised
    type "...end"
    return
  }
  type "Begin..."
  set ::recording 1
  .top.recprog configure -relief sunken
  .top.buffer.entry delete 0 end
}

proc prprog { } {
  type "Listing..."
  set maxj [ .prog.list.list index end ]
  for { set j 0 } { $j < $maxj } { incr j } {
    set ins [ .prog.list.list get $j ]
    type "$j: $ins"
  }
  type "...end"
}

proc paperadv { } { type "" }
proc paperclr { } {
  .top.printarea.text delete 1.0 end
  set ::value ""
}
proc papersav { } {
  set tapename [ tk_getSaveFile -defaultextension .txt \
    -initialdir . -parent .top -title "Save Paper Tape..." ]
  if { $tapename ne "" } {
    set f [ open $tapename w ]
    fconfigure $f -encoding utf-8
    set tape [ .top.printarea.text get 1.0 end ]
    puts $f $tape
    close $f
  }
}

proc page { number } {
  if { $::curpage != 0 } {
    grid forget .top.about.pg${::curpage}txt
    .top.about.pg${::curpage} configure -relief groove
  }
  set ::curpage $number
  grid .top.about.pg${number}txt -row 3 -column 0 -columnspan 6 -rowspan 20 -sticky news
  .top.about.pg${::curpage} configure -relief solid
}

proc manual { } {
  set savename [ tk_getSaveFile -defaultextension pdf \
    -initialfile olpro101.pdf \
    -parent .top -title "Save Manual To..." ]
  if { $savename ne "" } {
    file copy -force $::manpdf $savename
  }
}

proc prefs { } {
  destroy .top.options
  toplevel .top.options
  label .top.options.title -text "PROGRAMMA-101\nSIMULATOR OPTIONS"
  grid .top.options.title -row 0 -column 0 -columnspan 2
  checkbutton .top.options.animation \
    -variable ::animation -text "Card Animation"
  grid .top.options.animation -row 1 -column 0
  button .top.options.close -text "Close Options" -command {
    destroy .top.options 
  }
  button .top.options.manual -text "Save Manual" -command manual
  grid .top.options.manual -row 2 -column 0
  grid .top.options.close -row 3 -column 0 -columnspan 2
}

proc about { } {
  set tabs \t
  if { $::size == 100 } { set tabs \t\t }
  destroy .top.about
  toplevel .top.about
  label .top.about.title -text "OLIVETTI-UNDERWOOD\nPROGRAMMA 101" \
    -font $::HLfn
  grid .top.about.title -row 0 -column 0 -columnspan 5
  label .top.about.blurb -text "by Larry Smith" -font $::HLfn
  grid .top.about.blurb -row 1 -column 0 -columnspan 5
  button .top.about.pg1 -command { page 1 } -text "Credits" \
    -font $::HLfn -relief groove
  grid .top.about.pg1 -row 2 -column 0
  button .top.about.pg2 -command { page 2 } -text "Machine" \
    -font $::HLfn -relief groove
  grid .top.about.pg2 -row 2 -column 1
  button .top.about.pg3 -command { page 3 } -text "Simulator" \
    -font $::HLfn -relief groove
  grid .top.about.pg3 -row 2 -column 2
  button .top.about.pg4 -command { page 4 } -text "Basic Ops" \
    -font $::HLfn -relief groove
  grid .top.about.pg4 -row 2 -column 3
  button .top.about.pg5 -command { page 5 } -text "Extensions" \
    -font $::HLfn -relief groove
  grid .top.about.pg5 -row 2 -column 4
  label .top.about.pg1txt -width 66 -bd 5 -relief solid -justify left -font $::abtfn -text "© 2009 by Larry Smith\n\nWritten for Marjie Smith, my wife, for her birthday.  The Olivetti\nProgramma 101 was the first real computer I ever used and it was\nthe first Marjie ever used, too.  It has great nostalgic value for us.\n\n\n\n\n\n\n"
  label .top.about.pg2txt -width 66 -bd 5 -relief solid -justify left -font $::abtfn -text "The Programma 101 was the first machine to be marketed as\n\"microcomputer\".  It was built with many transistors worth of\ndiscrete logic, and used a delay line to implement the memory\nregisters.  By modern standards it was limited - five registers\n(B, C, D, E and F) which could each be split in two if you could\nlive with half the accuracy).  It stored just 48 instructions in\nits program, and began eating up registers F, E, and D if you\nexceeded that number, to a maximum of 120 instructions with\nonly two registers left.\n\n\n"
  label .top.about.pg3txt -width 66 -bd 5 -relief solid -justify left -font $::abtfn -text "The simulator emulates the original machine, but it does not\nenforce its' limits.  That is to say, you can split the F register\ninto F and f (F-split) but they are not reduced in size, each is\nreally a separate register.\n\nThe program is also not limited to 48 or 120 steps, it can be any\nlength, and the F, E and D registers are never used.\n\n\n\n\n"
  label .top.about.pg4txt -width 66 -bd 5 -relief solid -justify left -font $::abtfn -text "\[To A\u2193\]\tdown\tTransfers named register to accumulator.\n\[M\u2191\]\tup\tMoves contents of M (keyboard) register to named reg.\n\[A\u2195\]\tswap\tSwitches contents of named register and accumulator.\n\[\u221a\]\tsqrt\tLoads accumulator with the square root of the reg.\n\[\u2014\]\tminus\tSubtracts named reg from accumulator.\n\[\u00d7\]\ttimes\tMultiplies named reg by accumulator.\n\[+\]\tplus\tAdds named reg to accumulator.\n\[\u00f7\]\tdivide\tDivides accumulator by named reg.\n\[*\]\tzero\tStores 0 in named reg.\n\[\u25c7\]\tprreg\tPrints the named reg.\n\[S\]\tstart\tStarts execution at current program step.\n\[/\]\tsplit\tUsed to address split registers a (A/), b (B/) etc."
  label .top.about.pg5txt -width 66 -bd 5 -relief solid -justify left -font $::abtfn -text "Buttons with a blue background are for extensions to the machine.\n\nSave Tape\tSaves contents of display tape to utf-8 text file.\nClear Tape\tClears display tape.\nPush Regs\tSave all regs but A, R & M to internal stack.\nPull Regs\t\tRestore all regs but A, R & M from internal stack.\nProg Library\tSets directory to search for program cards.\nSave Card${tabs}Saves current program to program card.\nShow Labels\tDisplays window to permit editing VXYZ labels.\nShow Regs\tDisplays window showing current contents of regs.\nShow Prog\tDisplays window showing current program.\n"
  button .top.about.exit -text "Exit Simulator" \
    -command off -border 3 -font $::abtfn
  grid .top.about.exit -row 23 -column 1
  button .top.about.close -text "Close About..." \
    -command { destroy .top.about } -border 3 -font $::abtfn
  grid .top.about.close -row 23 -column 3
  page 1
  center .top.about
}

proc setdec { args } {
  set ::tcl_precision $::numdecs
}

proc type { str { suffix "" } } {
  if { $suffix eq "" } {
    set prstr $str
  } else {
    set prstr "[format "%[= {$::dispch-4}].[set ::numdecs]f" $str] ${suffix}"
  }
  .top.printarea.text insert end \n$prstr
  .top.printarea.text see end
}

proc blink { } {
  if $::greenon return
  set ::greenon 1
  .top.green configure -bg $::brgreen
  set delay 300
  if !$::interactive { set delay 100 }
  after $delay {
    .top.green configure -bg $::dkgreen
  set ::greenon 0
  }
}

proc off { } { exit;
  if {[tk_messageBox -parent . -title "Are you sure?" \
         -icon question -type yesno -default no \
         -message "Exit P101 Simulator?"] eq "yes"
  } {
    exit
  }
}

proc redlight { code } {
  .top.green configure -bg $::dkgreen
  .top.red configure -bg $::brred
  if $::interactive { type $code E! } else { type $code PC }
  set ::interactive 1
  foreach w $::btnlist { $w configure -state disabled }
  set ::register(A) 0.0
}

proc jump { label } {
  set ::interactive 0
  set reg [ string index $label 0 ]
  set cond 0
  if { [ string first $reg "/cdr" ] != -1 } {
    set cond 1
  }
  set pc $::pc
  if { !$cond || ($::register(A) > 0) } {
    set pc [ lsearch -exact $::program $::jumps($label) ]
  }
  if { $pc == -1 } {
    set ::interactive 1
    redlight $::pc
    set ::pc 0
    return 0
  }
  set ::pc $pc
  return 1
}

proc isjump { label } {
  set result 0
  if { ($label ne "") && ($label ne " S") } {
    set reg [ string index $label 0 ]
    set cmd [ string index $label 1 ]
    if { ([ string first $cmd "VWYZ" ] != -1 ) &&
         ([ string first $reg " MCDR/cdr" ] != -1 )
       } {
      set result 1
    }
  }
  return $result
}

proc start { { label "" } } {
  if $::recording { set ::curbtn start ; return }
  if { ![info exists ::pc] || ($::pc eq "") } { set ::pc 0 }
  set ::interactive 0
  set ::program [ .prog.list.list get 0 end ]
  if { $label ne "" } { jump $label }
  while 1 {
    blink
    set ins [ lindex $::program $::pc ]
    set cmd [ string index $ins end ]
    set reg [ string index $ins end-1 ]
    incr ::pc
    if { $ins eq "end" } {
      if [ pullstate ] continue
      set ::pc 0
      return
    } elseif { $ins eq " S" } { return
    } elseif { [string first $cmd "VWYZ" ] !=-1 } {
      # either label or branch.  Labels are ignored
      if [ isjump $ins ] {
        if ![jump $ins] return
      }
    } else {
      set cmd $::op2cmd($cmd)
      set value [ string range $ins 0 end-2 ]
      if { $value ne "" } { set ::register(M) $value }
      $cmd $reg
      if $::interactive return
    }
  }
}

proc interactive { str reg op } {
  if $::interactive { type $str ${reg}$op }
  blink
}

proc nextkey { } {
  while 1 {
    vwait ::curbtn
    if { [lsearch $::immediate $::curbtn] != -1 } {
      $::curbtn
    } else {
      set btntype [ string range $::curbtn 0 2 ]
      if { ($btntype eq "num") || ($btntype eq "reg") } {
        set ::curbtn [ string index $::curbtn end ]
        if { $::curbtn eq "!" } { set ::curbtn . }
      } elseif { [ string first $::curbtn "vwyz" ] != -1 } {
        set ::curbtn [ string toupper $::curbtn ]
      } elseif [ info exists ::cmd2op($::curbtn) ] {
        set ::curbtn $::cmd2op($::curbtn)
      }
      return $::curbtn
    }
  }
}

proc backsp { } {
  .top.buffer.entry delete [= {[string length [.top.buffer.entry get]]-1}]
}

proc cmdloop { } {
  set delbuffer 1
  while 1 {
    set reg ""
    set btn [nextkey]
    if {[string first $btn "0123456789.-" ] != -1} {
      if $delbuffer {
        .top.buffer.entry delete 0 end
        set delbuffer 0
        set havedec 0
      }
      if { $btn eq "." } {
        if !$::havedec {
          set ::havedec 1
          .top.buffer.entry insert end .
        }
      } elseif { $btn eq "-" } {
        if {[ string index $::buffer 0 ] eq "-" } {
          .top.buffer.entry delete 0 1
        } else {
          .top.buffer.entry insert 0 -
        }
      } else {
        .top.buffer.entry insert end $btn
      }
      continue
    }
    set value ""
    if { $::buffer ne "" } {
      if { [ string first "." $::buffer ] != -1 } {
        set ::buffer ${::buffer}.0
      }
      set value $::buffer
      if $::interactive { set ::register(M) $value }
    }
    set delbuffer 1
    # value (if any) dealt with.  btn should now be op, split or reg
    if {[string first $btn "ABCDEFMR/"] != -1 } {
      # it's a register name
      set reg [ string index $btn end ]
      set btn [ nextkey ]  ;# look for op or split
      if { $btn eq "/" } {
        set reg [ string tolower $reg ]
        if { $reg eq "m" } { set reg / }
        set btn [nextkey] ;# op MUST follow now
      }
    }
    # we get here we have a reg and op is in btn
    if { $reg eq "" } { set reg M } ;# default register
    if $::recording {
      if { [string first $btn "VWXZ" ] != -1 } {
        if { $reg eq "M" } { set reg " " }
        insins "$value$reg$btn"
      } else {
        if { $btn eq "S" } { set reg " " }
        insins "$value$reg$btn"
      }
      .top.buffer.entry delete 0 end
      incr ::pc
    } else {
      if [ isjump $reg$btn ] {
        start $reg$btn
        set ::interactive 1
        .top.buffer.entry delete 0 end
        .top.buffer.entry insert end $::register(M)
      } else {
            if { [catch { $::op2cmd($btn) $reg } err ] } {
              $btn
        }
          }
    }
  }
}

proc loadlistbox { w values } {
 set j true
 set indx 0
 $w.list delete 0 end
 $w.linecount delete 0 end
 foreach i $values {
   $w.list insert end $i
   $w.linecount insert end $indx
   if {$j} {
     set j false
     $w.list itemconfigure $indx -background #ffffdd
   } else {
     set j true
   }
   incr indx
  }
}

proc scrolledlistbox { w width height values cmd { font "" }} {
log "height is $height"
 if { $font eq "" } { set font $::txtfn } else { set font $::brfn }
 frame $w
 listbox $w.list -width $::brwidth -height $::brheight -font $font
 listbox $w.linecount -width 4 -height $::brheight -font $font
 $w.list configure -yscrollcommand "$w.scrl set"
 #$w.linecount configure -yscrollcommand "$w.scrl set"
 scrollbar $w.scrl -command "$w.list yview; $w.linecount yview"
 pack $w.scrl -side right -fill y
 pack $w.linecount -side left -fill y
 pack $w.list -side left -fill both -expand 1
 loadlistbox $w $values
 # bindings
 #
 # this will obtain the item clicked, and then pass
 # the value onto the proc specified in the variable cmd.
 eval "bind $w.list <ButtonRelease-1> \{$cmd \[\%\W get \@\%x,\%y\]\}"
 # return the widget path
 return $w
}

proc scrolledtextarea {w l t r b } {
  set width [= {round(($r-$l)*$::dispw)}]
  set height [= {round(($b-$t)*$::disph)}]
  set x [= {round($l*$::dispw)}]
  set y [= {round($t*$::disph)}]
  frame $w -width $width -height $height -bd 2 -bg white
  place $w -x $x -y $y
  scrollbar $w.vscroll -orient vertical -command [ list $w.text yview ]
  scrollbar $w.hscroll -orient horizontal -command [ list $w.text xview ]
  text $w.text -yscrollcommand [ list $w.vscroll set ] \
    -xscrollcommand [ list $w.hscroll set ] \
    -font $::txtfn -bg white -width 1 -height 4 -width $::dispch
  pack $w.vscroll -side right -fill y
  pack $w.hscroll -side top -fill x
  pack $w.text -side left
}

proc uptodate {filename {time 0}} {
  set filename [file join [pwd] $filename]
  set mtime [file mtime $filename]
  if {$mtime > $time} {source $filename}
  after 1000 [list uptodate $filename $mtime]
} ;#RS

proc reloadlib {} {
  set proglist ""
  catch { set proglist [glob -directory $::library *.p101] }
  .top.cardlist.list delete 0 end
  foreach file $proglist {
    .top.cardlist.list insert end $file
  }
  renum .top.cardlist
}

proc proglib {} {
  set newlib [ tk_chooseDirectory  -initialdir $::library \
    -mustexist 1 -parent .top -title "Library Directory" ]
  if { $newlib ne "" } { set ::library $newlib }
}

proc savecard {} {
  set progname [ tk_getSaveFile -defaultextension p101 \
    -initialdir $::library -parent .top -title "Save Program To..." ]
  if { $progname ne "" } {
    set f [ open $progname w ]
    fconfigure $f -encoding utf-8
    puts $f "[ .prog.list.list get 0 end ]"
    foreach reg { A B b C c D d E e F f R } {
      puts $f $::register($reg)
    }
    foreach lbl { v w y z } {
      set text [.vwyz.${lbl}txt get 1.0 end ]
      set text [ split $text \n ]
      set text [ join $text "\\n" ]
      puts $f $text
    }
    puts $f $::pc
    puts $f $::numdecs
    close $f
  }
  animatecard 1
  reloadlib
}

proc runcard { args } { 
  set cardname $::register(M)
  set ::register(M) 0.0
  pushstate $cardname
}

set ::animating 0
proc animatecard { { reverse 0 } } {
  if $::animating return
  set ::animating 1
  destroy .card
  if !$::animation return
  set cardw [ image width card ]
  set cardh [ image height card ]
  toplevel .card
  wm overrideredirect .card 1
  canvas .card.c -width $cardw -height $cardh
  pack .card.c
  .card.c create image 0 0 -image card -anchor nw
  update
  set leftsh [= {round($::dispw*0.6955)}]
  set bottomsh [= {round($::disph*0.031 )}]
  set left [= {[ winfo rootx .top ] + $leftsh}]
  set showlabels 0
  if $reverse {
    set bottom [= {[winfo rooty .top.cdrdr] + $bottomsh}]
    set curh 1
    while { $curh < $cardh } {
      wm geometry .card ${cardw}x$curh+$left+$bottom
      update
      incr curh
      incr bottom -1
    }
  } else {
    set rooty [winfo rooty .top.cdrdr]
    set cardh [winfo height .card]
    set bottom [= {$rooty-$cardh+$bottomsh}]
    set curh $cardh
    while { $curh > 0 } {
      wm geometry .card ${cardw}x$curh+$left+$bottom
      update
      incr curh -1
      incr bottom
    }
  }
  set ::animating 0
  destroy .card
  update
}

proc loadprog { } {
  foreach ins $::program {
    .prog.list.list insert end $ins
  }
}

proc loadcard { cardname } {
  set h [winfo height .top]
  set w [winfo width .top]
  if $::recording {
    insins "$cardname @"
    return
  }
  if { $cardname eq "" } return
  set f [ open $cardname r ]
  fconfigure $f -encoding utf-8
  set ::program [ gets $f ]  
  foreach reg { A B b C c D d E e F f R} {
    set ::register($reg) [ gets $f ]
  }
  foreach lbl { v w y z } {
    .vwyz.${lbl}txt delete 1.0 end
    eval set lbltxt [ gets $f ]
    set lbltxt [ string trim $lbltxt ]
    .vwyz.${lbl}txt insert end $lbltxt
  }
  updlbls
  set ::pc [ gets $f ]
  set ::numdecs [ gets $f ]
  close $f
  .prog.list.list delete 0 end
  loadprog
  renum .prog.list
  wm geometry .top ${w}x$h
  animatecard
}

proc updlbls { } {
  .top.face delete vwxylbls
  set y [= {round($::dispw*.72)}]
  foreach { lbl offset } { v .720 w .800 y .880 z .960 } {
    set x [= {round($offset*$::dispw)}]
    set lbltxt [ .vwyz.${lbl}txt get 1.0 end ]
    if { $lbltxt ne "" } {
      .top.face create text $x $y -fill black -font $::lblfn \
        -tags vwxylbls -anchor s -justify center -text $lbltxt
    }
  }
  .top.face raise vwxylbls
}

proc setpc { instr } {
  set ::pc [ .prog.list.list curselection ]
}

proc showprog { args } {
  if $::progshowing {
    wm withdraw .prog
    set ::progshowing 0
    .top.showprog configure -text "Show\nProg"
  } else {
    wm deiconify .prog
    set ::progshowing 1
    .top.showprog configure -text "Hide\nProg"
  }
}

proc showlabels { args } {
  if $::labelshowing {
    set ::labelshowing 0
    wm withdraw .vwyz
  } else {
    set ::labelshowing 1
    wm deiconify .vwyz
  }
}

proc pushregs { } {
  if $::recording { set ::curbtn pushregs ; return }
  set state [ list \
    $::register(B) $::register(b) $::register(C) $::register(c) \
    $::register(D) $::register(d) $::register(E) $::register(e) \
    $::register(F) $::register(f) ]
  lappend ::regstack $state
}

proc pullregs { } {
  if $::recording { set ::curbtn pullregs ; return }
  if { $::regstack eq {} } { return 0 }
  set state [lindex end $::regstack]
  set ::stack [lrange $::regstack 0 end-1]
  foreach [list \
    ::register(B) ::register(b) ::register(C) ::register(c) \
    ::register(D) ::register(d) ::register(E) ::register(e) \
    ::register(F) ::register(f) ] \
  $state break
  return 1
}

# push and pull save everything but registers AM&R, which can
# be used to pass results back to a previous program
proc pushstate { newcard } {
  lappend ::stack $::program
  lappend ::stack $::pc
  pushregs
  loadcard $newcard
}

proc pullstate { } {
  if { $::stack eq {} } { return 0 }
  set ::pc [lindex end $::stack]
  set ::stack [lrange $::regstack 0 end-1]
  set ::program [lindex end-1 $::stack]
  set ::stack [lrange $::regstack 0 end-1]
  pullregs
  return 1
}

proc showregs { args } {
  if !$::regssetup {
    foreach reg { A B b C c D d E e F f M R } {
      .regs.reglist.linecount insert end $reg
      .regs.reglist.list insert end $::register($reg)
      trace add variable ::register($reg) write updregs
    }
    set ::regssetup 1
  }
  if $::regsshowing {
    wm withdraw .regs
    set ::regsshowing 0
    .top.showregs configure -text "Show\nRegs"
  } else {
    wm deiconify .regs
    set ::regsshowing 1
    .top.showregs configure -text "Hide\nRegs"
  }
}

proc updregs { args } {
  .regs.reglist.list delete 0 end
  foreach reg { A B b C c D d E e F f M R } {
    .regs.reglist.list insert end $::register($reg)
  }
}

proc center { w { width 0 } { height 0 } } {
  update
  if { $width == 0 } { set width [winfo width $w] }
  if { $height == 0 } { set height [winfo height $w] }
  set x [= {([winfo vrootwidth  $w] - $width  ) / 2 }]
  set y [= {([winfo vrootheight $w] - $height ) / 2 }]
  wm geometry $w ${width}x${height}+${x}+${y}
}

proc showsplash { } {
  destroy .splash
  toplevel .splash
  wm overrideredirect .splash 1
  canvas .splash.c
  pack .splash.c -side top -fill both -expand 1
  image create photo splash -file $::runfrom/images/splash.gif
  .splash.c create image 0 0 -image splash -anchor nw
  center .splash 600 655
  update
  after 3000 {
    destroy .splash
  }
}

proc buildvwyz { } {
  destroy .vwyz
  toplevel .vwyz
  wm protocol .vwyz WM_DELETE_WINDOW showlabels
  wm withdraw .vwyz
  label .vwyz.title -text "Labels:" -font regfn
  grid .vwyz.title -row 0 -column 0 -columnspan 2

  label .vwyz.v -text "V:" -font regfn
  grid .vwyz.v -row 1 -column 0 -sticky news
  label .vwyz.w -text "W:" -font regfn
  grid .vwyz.w -row 2 -column 0 -sticky news
  label .vwyz.y -text "Y:" -font regfn
  grid .vwyz.y -row 3 -column 0 -sticky news
  label .vwyz.z -text "Z:" -font regfn
  grid .vwyz.z -row 4  -column 0 -sticky news

  text .vwyz.vtxt -width 10 -font regfn -height 5
  grid .vwyz.vtxt -row 1 -column 1 -sticky news
  text .vwyz.wtxt -width 10 -font regfn -height 5
  grid .vwyz.wtxt -row 2 -column 1 -sticky news
  text .vwyz.ytxt -width 10 -font regfn -height 5
  grid .vwyz.ytxt -row 3 -column 1 -sticky news
  text .vwyz.ztxt -width 10 -font regfn -height 5
  grid .vwyz.ztxt -row 4 -column 1 -sticky news

  button .vwyz.updlbls -font regfn -command updlbls \
    -text "Update Labels"
  grid .vwyz.updlbls -row 5 -column 0 -columnspan 2 -sticky ew
  update
}

proc buildprog { } {
  destroy .prog
  toplevel .prog
  wm protocol .prog WM_DELETE_WINDOW showprog
  wm withdraw .prog
  scrolledlistbox .prog.list 20 10 "" setpc
  .prog.list.list insert end end; renum .prog.list
  label .prog.label -text "Program:" -anchor w
  pack .prog.label .prog.list
  update
}

proc buildregs { } {
  destroy .regs
  toplevel .regs
  wm protocol .regs WM_DELETE_WINDOW showregs
  wm withdraw .regs
  scrolledlistbox .regs.reglist 20 13 "" ""
  label .regs.label -text "Registers:" -anchor w
  pack .regs.label .regs.reglist
  update
}

proc buildgui { args } {
  global size dispw disph
  destroy .top
  toplevel .top
  wm protocol .top WM_DELETE_WINDOW { off }
  image create photo card -file $::runfrom/images/p101card-${size}%.gif
  image create photo p101 -file $::runfrom/images/p101.gif
  image create photo cr -file $::runfrom/images/cardreader-${size}%.gif
  image create photo 1xparentpx -file $::runfrom/images/1xparentpx.gif
  image create photo p101face -file $::runfrom/images/P101-${size}%.gif
  image create photo btnup -file $::runfrom/images/P101-${size}%.gif
  #image create photo btndn \
    -file $::runfrom/images/P101-${size}%-dark.png
  set dispw [ image width p101face ]
  set disph [ image height p101face ]
  switch $size {
    50  { setfonts 18 2 32 33 6 6 8 8 8 8 6 }
    75  { setfonts 22 4 27 29 10 8 14 14 12 12 8 }
    100 { setfonts 20 4 25 28 16 12 20 20 16 16 12 }
  }
  update
  set btns [ list \
    .top.about 0.0852 0.0 0.2335 0.1260 \
    .top.paperadv 0.01052 0.2087 0.0727 0.3930 \
    .top.prefs 0.0096 0.4715 0.0746 0.5556 \
    .top.reset 0.0096 0.5840 0.0746 0.6680 \
    .top.off 0.00956 0.6965 0.0746 0.7805 \
    .top.regF 0.0852 0.4580 0.1703 0.5704 \
    .top.regE 0.0852 0.5705 0.1703 0.6829 \
    .top.regD 0.0852 0.6830 0.1703 0.7953 \
    .top.regC 0.0852 0.7954 0.1703 0.9078 \
    .top.showlabels 0.0852 0.9079 0.1703 0.9986 \
    .top.up 0.1703 0.4580 0.3388 0.5704 \
    .top.regB 0.1703 0.6830 0.3388 0.7953 \
    .top.clear 0.1703 0.5705 0.3388 0.6829 \
    .top.reg/ 0.1703 0.7954 0.3388 0.9078 \
    .top.showregs 0.1703 0.9079 0.2545 0.9986 \
    .top.showprog 0.2555 0.9079 0.3388 0.9986 \
    .top.num7 0.3426 0.4580 0.4230 0.5704 \
    .top.num4 0.3426 0.5705 0.4230 0.6829 \
    .top.num1 0.3426 0.6830 0.4230 0.7953 \
    .top.num0 0.3426 0.7954 0.4230 0.9078 \
    .top.pushregs 0.3426 0.9079 0.4230 0.9986 \
    .top.num8 0.4239 0.4580 0.5091 0.5704 \
    .top.num5 0.4239 0.5705 0.5091 0.6829 \
    .top.num2 0.4239 0.6830 0.5091 0.7953 \
    .top.num! 0.4239 0.7954 0.5091 0.9078 \
    .top.pullregs 0.4239 0.9097 0.5091 0.9986 \
    .top.num9 0.5100 0.4580 0.5943 0.5704 \
    .top.num6 0.5100 0.5705 0.5943 0.6929 \
    .top.num3 0.5100 0.6830 0.5943 0.7953 \
    .top.num- 0.5100 0.7954 0.5943 0.9078 \
    .top.proglib 0.5100 0.9097 0.5942 0.9986 \
    .top.start 0.5962 0.4580 0.6804 0.9078 \
    .top.savecard 0.5962 0.9097 0.6804 0.9986 \
    .top.recprog 0.6813 0.35 0.839 0.41 \
    .top.down 0.6813 0.4580 0.8402 0.5704 \
    .top.minus 0.6813 0.5705 0.7590 0.6929 \
    .top.plus 0.6813 0.6830 0.7590 0.7953 \
    .top.v 0.6813 0.7954 0.7664 0.8550 \
    .top.times 0.7608 0.5705 0.8402 0.6929 \
    .top.divide 0.7608 0.6830 0.8402 0.7953 \
    .top.w 0.7608 0.7954 0.9262 0.8564 \
    .top.prprog 0.8411 0.35 0.999 0.41 \
    .top.swap 0.8411 0.4580 0.9196 0.8550 \
    .top.regA 0.8411 0.5705 0.9196 0.6929 \
    .top.regR 0.8411 0.6830 0.9196 0.7953 \
    .top.y 0.8411 0.7954 0.9196 0.8564 \
    .top.sqrt 0.9206 0.4580 0.9990 0.8550 \
    .top.prreg 0.9206 0.5705 0.9990 0.6929 \
    .top.zero 0.9206 0.6830 0.9990 0.7953 \
    .top.z 0.9206 0.7954 0.9990 .8580 \
    .top.papersav 0.0105 0.1463 0.0727 0.2073 \
    .top.paperclr 0.0105 0.3957 0.0727 0.4566 \
    .top.backsp 0.5962 0.25 0.6804 0.455 \
  ]
  canvas .top.face -width $dispw -height $disph
  .top.face create image 0 0 -image p101face -anchor nw
  place .top.face -x 0 -y 0
  update
  center .top $dispw $disph
  set ::btnlist {}
  foreach { name l t r b } $btns {
    set l [= {round($l*$::dispw)}]
    set t [= {round($t*$::disph)}]
    set r [= {round($r*$::dispw)}]
    set b [= {round($b*$::disph)}]
    image create photo face$name
    face$name copy btnup -from $l $t $r $b
    set cmd [ list set ::curbtn $name ]
    set code [ string range [ file extension $name ] 1 end ]
    button $name -image face$name -command [list set ::curbtn $code] \
      -bd 0 -highlightthickness 0 -activebackground #0000ff
    place $name -x $l -y $t 
        raise $name
    if { ($name ne ".top.clear") && ($name ne ".top.reset") } {
      lappend ::btnlist $name
    }
  }
  # printarea
  foreach { l t r b } { 0.0852 0.127 0.67 0.29 } break
  scrolledtextarea .top.printarea $l $t $r $b
  # buffer
  foreach { x y } { 0.0855 0.390 } break
  frame .top.buffer -bd 2
  place .top.buffer -x [= {round($x*$dispw)}] -y [= {round($y*$disph)}]
  entry .top.buffer.entry -font $::txtfn -textvariable ::buffer -width $::bufwidth
  pack .top.buffer.entry -fill both
  # cardlist
  foreach { x y } { .6813 .125 } break
  scrolledlistbox .top.cardlist $::brwidth $::brheight "" loadcard $::brfn
  place .top.cardlist -x [= {round($x*$dispw)}] -y [= {round($y*$disph)}]
  # card reader
  foreach { w h x y } { 0 0 .681 .2735 } break
  label .top.cdrdr -image cr -anchor nw -bd 0
  place .top.cdrdr -x [= {round($x*$dispw)}] -y [= {round($y*$disph)}]
  # green light
  foreach { w h x y } { .18 .06 .6825 .01 } break
  if { $::size == 50 } { set w .18 }
  label .top.green -bg $::dkgreen -anchor center  -image 1xparentpx \
    -width [= {round($dispw*$w)}] -height [= {round($disph*$h)}] -bd 10 \
    -relief raised
  place .top.green -x [= {round($x*$dispw)}] -y [= {round($y*$disph)}]
  # red light
  foreach { w h x y } { .07 .06 .9 .01 } break
  label .top.red -bg $::dkred -anchor center -image 1xparentpx \
    -width [= {round($dispw*$w)}] -height [= {round($disph*$h)}] -bd 10 \
    -relief raised
  place .top.red -x [= {round($x*$dispw)}] -y [= {round($y*$disph)}]
  # decimal wheel
  foreach { w h x y } { 2 1 .018 .8821 } break
  spinbox .top.decset -from 0 -to 15 -wrap 1 -font $::spfn \
    -width 2 -command setdec -textvariable numdecs
  place .top.decset -x [= {round($x*$dispw)}] -y [= {round($y*$disph)}]

  if $::needreset {
    set ::needreset 0
    buildregs
    buildvwyz
    buildprog
    reset 1
  } else {
    loadprog
  }
  reloadlib
  update
}

proc setsize { {size 0 } } {
  if { $size == 0 } {
    destroy .size
    toplevel .size
    label .size.msg -text "Simulator Display Size?"
    grid .size.msg -row 0 -column 0 -columnspan 4
    button .size.100 -text "100%" -command { setsize 100 }
    grid .size.100 -row 1 -column 0
    button .size.75 -text "75%" -command { setsize 75 }
    grid .size.75 -row 1 -column 1
    button .size.50 -text "50%" -command { setsize 50 }
    grid .size.50 -row 1 -column 2
    button .size.cancel -text "Exit" -command exit
    grid .size.cancel -row 1 -column 3
    center .size
        after 1000 { wm deiconify .size }
  } else {
    destroy .size
    set ::size $size
  }
}

# initialize
package require Tk
wm withdraw .
#set runfrom .
set runfrom p101.exe
set manpdf $::runfrom/olpro101.pdf 
interp alias {} = {} expr
encoding system utf-8
set dkgreen #179fa2
set brgreen #879fa2
set dkred #880000
set brred #ff0000
set btnlist {}
set program {}
set stack {}
set regstack {}
array set op2cmd {
  \u2193 down
  \u2191 up
  \u2195 swap
  \u221a sqrt
  \u2014 minus
  \u00d7 times
  +      plus
  \u00f7 divide
  *      zero
  \u25c7 prreg
  S      start
  /      split
  @      runcard
  (      pushregs
  )      pullregs
}
array set cmd2op {
  down     \u2193
  up       \u2191
  swap     \u2195
  sqrt     \u221a
  minus    \u2014
  times    \u00d7
  plus     +
  divide   \u00f7
  zero     *
  prreg    \u25c7
  start    S
  split    /
  runcard  @
  pushregs (
  pullregs )
}
set uselog 1
set logopen 0
set logfile ""
set recording 0
set curpage 0
set animation 1
set interactive 1
set greenon 0
array set jumps {
  " V" AV
  " W" AW
  " Y" AY
  " Z" AZ
  MV AV
  MW AW
  MY AY
  MZ AZ
  CW BW
  CY BY
  CZ BZ
  DV EV
  DW EW
  DY EY
  DZ EZ
  RV FV
  RW FW
  RY FY
  RZ FZ
  /V aV
  /W aW
  /Y aY
  /Z aZ
  cV bV
  cW bW
  cY bY
  cZ bZ
  dV eV
  dW eW
  dY eY
  dZ eZ
  rV fV
  rW fW
  rY fY
  rZ fZ
}
set immediate [ list recprog prprog prefs reset off clear start \
  papersav paperclr showlabels showregs showprog pushregs pullregs \
  proglib savecard about backsp ]
set havedec 0
set progshowing 0
set progshowing 0
set labelshowing 0
set regsshowing 0
set regssetup 0
set regsshowing 0
uptodate [info script] [file mtime [info script]]
set numdecs 4; setdec
set curbtn ""
trace add variable size write buildgui
set needreset 1
set library .
showsplash
after 2000
setsize
cmdloop