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)