[comp.lang.smalltalk] Smalltalk Notes 5: UNIXating your PS

rentsch@unc.UUCP (Tim Rentsch) (11/11/86)

Copyright(c) 1986, by Tim Rentsch

Copyright Notice:  This memo is Copyright(c) 1986, by Tim Rentsch.
Copies may be made and distributed provided all of the following
conditions are met:  one, the copyright notice be included in its
entirety; two, the memo be copied (or distributed) in its entirety;
three, the memo be copied (or distributed) individually, i.e., not in
collection with other material; four, copies be made and distributed
one at a time, i.e., copies cannot be made or distributed in batches
of more than one; five, copies may not be sold -- distributing copies
is to be done at no cost to the recipients of the copies.  (End of
conditions, and end of copyright notice.)  



Note # 5:  UNIXating your PS Smalltalk
======================================

(Again, see disclaimer from note # 4.  This code was necessary for
my version of PS, it may not be necessary for your version.)

PS as we got it had the annoying property that Character cr was
different from Unix \n, with the result that most of the files
produced by PS smalltalk were essentially unreadable except from
within Smalltalk.

Well, that just could not be.  First we had to have normal unix
files to do printing (by swiping the printOut code from BS).  This
is easy enough to do -- define UnixTranslatingFileStream which
translates CRs to NLs and vice versa, and you are all set.

Not being satisfied with this, and not being able to let well enough
alone, I floundered around until I found the code which would
transform an image so that the image used NLs rather than CRs.  The
result is given here.

For general information, my standard image here has had this code
run on it.  It seems quite reliable, and there have been no
mysterious crashes or strange behavior.  In short, it seems as if
the code which transformed CRs to NLs did exactly the right thing.

Because this is a fairly drastic step, the code will be given with
comments added (in Smalltalk comment delimiters, natch, with "nil!"
at the end to make the evaluator happy).  Furthermore, be careful
when testing this code -- try it on a test copy, etc.

WARNING -- the code given has been hand edited, and the result has
not been checked by filing it in.  It may work just by cutting and
filing in, but it might have errors.  The pieces should be checked
for legal smalltalk syntax before putting together and filing in.
Furthermore, to format this for transmission on netnews, tabs were
changed to three spaces, and various other ad hoc changes were made.
For code that is being installed in the system, it is probably
better if you replace that code with code copied from your own
running Smalltalk, with the appropriate changes made.

With no further ado, the code starts.  Remember, to find
annotations, search for "nil!" (without the ""s).


'From UNC Smalltalk-80, version 2.1, of October 26, 1986 on
 5 November 1986 at 3:22:32 am'!



"First we swap CRs (Character value: 13) with NLs (Character value: 10)
 in all Strings.  It seems amazing that all Strings, and only all
 Strings, make up all of the objects which must be changed in this
 way.  But I looked carefully at all pointers to Character cr, and
 this is almost all of them.  (The remaining one is fixed below)."
nil!


   | c |
   String allInstances do: [ :s |
      1 to: s size do: [ :i |
         c _ (s at: i) asciiValue.
         c = 10
            ifTrue: [ s at: i put: (Character value: 13) ]
            ifFalse: [
               c = 13 ifTrue: [ s at: i put: (Character value: 10) ] ].
      ].
   ]. !
      


"Next we must of course fix the 'constant' Character cr to give a
 new line"   
nil!

!Character class methodsFor: 'accessing untypeable characters'!

cr
   "Answer the Character representing a carriage return."
   ^self value: 10! !



"TextConstants is the one other place that a CR must be changed into
 a new line.  Rather than do it directly, we change the method for
 TextConstants initialization, which must be done anyway, and then
 invoke it.  This way we know that the right value was put in by the
 initialization code."
nil!

!Text class methodsFor: 'class initialization'!

initTextConstants   "Text initTextConstants." 
   "Initialize constants shared by classes associated with text display, e.g.,
   space, tab, cr, bs, esc."

   | tempArray  | 
   TextConstants      at: #Space           put:   (32 asCharacter). 
   TextConstants      at: #Tab             put:   (9 asCharacter).
   TextConstants      at: #CR              put:   (10 asCharacter).
   TextConstants      at: #BS              put:   (8 asCharacter).
   TextConstants      at: #BS2             put:   (158 asCharacter).
   TextConstants      at: #Ctrlw           put:   (145 asCharacter).
   TextConstants      at: #ESC             put:   (160 asCharacter).
   TextConstants      at: #Cut             put:   (173 asCharacter).
   TextConstants      at: #Paste           put:   (30 asCharacter).
   TextConstants      at: #Ctrlt           put:   (11 asCharacter).
   TextConstants      at: #Ctrlf           put:   (12 asCharacter).
   TextConstants      at: #Ctrlz           put:   (26 asCharacter).

      "in case font doesn't have a width for space character"
      "some plausible numbers-- are there right ones?"
   TextConstants      at: #DefaultSpace       put:   4.
   TextConstants      at: #DefaultTab         put:   24.
   TextConstants      at: #DefaultLineGrid    put:   16.
   TextConstants      at: #DefaultBaseline    put:   12.
   TextConstants      at: #DefaultRule        put:   Form over.
   TextConstants      at: #DefaultMask        put:   Form black.

   TextConstants      at: #CtrlMinus       put:   (137 asCharacter).
   TextConstants      at: #CtrlShiftMinus  put:   (201 asCharacter).
   TextConstants      at: #Ctrlb           put:   (166 asCharacter).
   TextConstants      at: #CtrlB           put:   (230 asCharacter).
   TextConstants      at: #Ctrli           put:   (150 asCharacter).
   TextConstants      at: #CtrlI           put:   (214 asCharacter).
   TextConstants      at: #Ctrlx           put:   (151 asCharacter).



   tempArray _ Array new: Display width // DefaultTab.
   1 to: tempArray size do:
      [:i | tempArray 
            at: i 
            put: DefaultTab * i].
   TextConstants at: #DefaultTabsArray put: tempArray.
   tempArray _ Array new: 
               (Display width // DefaultTab) // 2.
   1 to: tempArray size do:
      [:i | tempArray 
            at: i 
            put: (Array with: (DefaultTab*i) with: (DefaultTab*i))].
   TextConstants at: #DefaultMarginTabsArray    put: tempArray.
   self initTextConstants2.! !



"Here is the call to invoke the redefined method"
nil!

Text initTextConstants !



"InputSensor is responsible for mapping characters from the given
 representation to the internal representation.  Since I don't have
 access to the interpreter, I just do the translation here.  Note
 that the only change is to map 13 --> 10;  10 was previously mapped
 to 30, and still is.  Thus 10 which is 'line feed' serves as Paste,
 which is 30, and 'return' gets mapped to new line."
nil!

!InputSensor class methodsFor: 'class initialization'!

newDefaultKeyboardMap
   | map pmap code shiftCode upCode char shiftChar1 shiftChar2 base array |
   map _ String new: 256 * 5.
   pmap _ InputState new keyboardMap.
   map atAllPut: 255 asCharacter.
   "Unassigned"
   0 to: 127 do: 
      [:key | 
      code _ pmap at: key + 1.
      shiftCode _ pmap at: key + 129.
      (code < 32 or: [code = 127])
         ifTrue: 
            ["ASCII control code"
            code _ #(8 9 10 13 27 127 ) indexOf: code. 
            code > 0
               ifTrue: 
                  [char _ (#(8 9 30 10 160 173 ) at: code) asCharacter.
                  map at: key + 1 put: char.
                  "Unshifted"
                  map at: key + 257 put: char.
                  "Shifted"
                  map at: key + 513 put: (code = 4
                        ifTrue: [141 asCharacter]
                        ifFalse: [char]).
                  "Control"
                  map at: key + 769 put: char]]
         ifFalse: 
            ["Printing ASCII character"
            "Special kludge for code 8r140"
            code = 96 ifTrue: [code _ 21].
            shiftCode = 96 ifTrue: [shiftCode _ 21].
            char _ code asCharacter.
            map at: key + 1 put: char.
            map at: key + 257 put: shiftCode asCharacter.
            upCode _ char asUppercase asciiValue.
            "Convert a..z to A..Z"
            "Control"
            (map at: key + 513) asciiValue = 255
               ifTrue: [
                  map
                     at: key + 513
                     put: (self asciiToControl: upCode) asCharacter].
            "Shift+control"
            (map at: key + 769) asciiValue = 255
               ifTrue: 
                  [shiftChar1 _ self asciiToShiftControl: upCode.
                  shiftChar2 _ self asciiToControl: shiftCode.
                  "The following conditional gives priority 
                  to
               control+(shift+char) over (control+shift)+char
               if 
                  there is a conflict, except for letters."
                  map
                     at: key + 769
                     put: ((shiftChar2 = 255 or: [char isLetter])
                        ifTrue: [shiftChar1]
                        ifFalse: [shiftChar2]) asCharacter]]].
   "Map the meta and unlabelled keys 1-for-1."
   128 to: 255 do: [:code | 1
         to: 1025
         by: 256
         do: [:part | map at: part + code put: code asCharacter]].
   "Handle the unlabelled keys on the PARC keyboard.
   Other keyboards 
   may have unlabelled keys too."
   "Shift"
   "Control"
   "Shift+control"
   #((0 (158 29 25 174 187 190 ) )
     (256 (22 29 25 238 253 254 ) )
     (512 (222 229 225 174 187 190 ) )
     (768 (22 24 25 238 253 254 ) ) ) do: 
      [:part | 
      base _ (part at: 1)
               + 140.
      array _ part at: 2.
      1 to: array size do: [:i |
         map at: base + i put: (array at: i) asCharacter]].
   "Upper case lock"
   0 to: 255 do: [:code |
      map at: code + 1025 put: (map at: code + 1) asUppercase].
   ^map! !



"Now be sure the modified code takes effect.  This is done by using
 the install method in InputSensor, which will build a new keyboard
 map using newDefaultKeyboardMap"
nil!

InputSensor install !



"Next we have to fix all the stopConditions stashed away in
 StrikeFonts and CharacterScanners (and their subclasses).
 CharacterScanners set their stop conditions from their StrikeFonts,
 and by using previous changed 'constants', so they are easy --
 the second block just sends 'setStopConditions' to each instance of
 the class in question.

 StrikeFonts (and its non-existant subclasses) is a little harder,
 since the stop conditions are "wired in", in effect.  Given the
 choice between installing a method in StrikeFont, and UGLY code
 which stomps on StrikeFonts instances state, I am proud to say I
 ignored the principles of object oriented programming, and went for
 the ugly code.  The 'instVarAt: 4' is the stopConditions instance
 variable in StrikeFont -- you might check your own image before
 proceeding.  To be safe the resulting variable really should be
 checked to be sure it is an array of the right size, but heck, the
 code here worked in my image."
nil!

   | todo stops |
   todo _ [ :class |
      class allInstances do: [ :font |
         stops _ font instVarAt: 4.
         (stops at: 14) == nil ifFalse: [
            stops at: 11 put: (stops at: 14).
            stops at: 14 put: nil.
         ].
      ] ].
   todo value: StrikeFont.
   StrikeFont allSubclassesDo: todo.   

   todo _ [ :class | 
      class allInstances do: [ :i | i setStopConditions ] ].
   todo value: CharacterScanner.
   CharacterScanner allSubclassesDo: todo. !



"Finally we must change the Sources files so that they have nl's
 instead of cr's in them.  The code for doing this is obvious, and
 it is slow.  NOTE:  this works only because exactly one character is
 being substituted for one other character.  If the character counts
 were not the same, all the pointers into the files would have to
 be changed.  Fortunately, we are just substituting one character
 for another."
nil!

   | newSources newChanges cr |
   cr _ Character value: 10.

   newSources _ (FileStream fileNamed: '##temp.sources').
   (SourceFiles at: 1) reset; do: [ :c |
      newSources nextPut:
         (c asciiValue = 13 ifTrue: [ cr ] ifFalse: [ c ]). ].
   newSources shorten; close.

   newChanges _ (FileStream fileNamed: '##temp.changes').
   (SourceFiles at: 2) reset; do: [ :c |
      newChanges nextPut:
         (c asciiValue = 13 ifTrue: [ cr ] ifFalse: [ c ]). ].
   newChanges shorten; close.

   SourceFiles at: 1 put: newSources.
   SourceFiles at: 2 put: newChanges.
   (SourceFiles at: 1) readOnly. !



"Now you are almost done.  To be really done, save the resultant
 image, complete with new image name, and delete the temporary
 sources files.  You now have a UNIXated image."
nil!



"End of UNIXating code."
nil!

(End of Note # 5)