LnSOS BOOT 1.1 SOS.KERNEL SOS KRNLI/O ERRORFILE 'SOS.KERNEL' NOT FOUND%INVALID KERNEL FILE: xةw,@  ȱlmi8#)!)  / / / C H E E R S ! A U T H O R ' S G U I D E  Articles and programs should be submitted on diskette. We will try to acknowledge your submission by mail as soon as it is received. Rejected submissions will be returned to the author.,T}!~()VERSAFORM"6@}!~'WELCOMEL}!~0,SETDEBUG.INV} !~SETDEBUG.MENU}} !~-SETDEBUG.TEXT}!~8+THREE.IDEAS7}!~:'THREEEZ (}!~$UTIL }!~(UTIL.DOC } !~,,LIST.MGR.DOC& } !~LIST.MGR.MENU0G} !~-LIST.MGR.TEXT2+T} !~+$NEWS] } !~: *PRINT.MENUed} !~/,SETDEBUG.DOCh4} !~6ARTICLES02u' -AUTHORS.GUIDE} !~6 CHEERS.BYLINES } !~ -CONTENTS.MENU )} !~ *DEBUG.CODE} !~.DEBUG.PAS.TEXT } !~-LIST.MGR.CODE >dLԡm#i㰼m#iЕOLԡȱfg hi !dLԡ憦  Ljmkm l y`2 Lԡ8(Je稽)ʈ@L We may also request revisions in text or programs. You will be notified by mail or phone if your submission is accepted, along with the approximate publication date. Our normal practice and preference is that Donovan's Reef shall retain copyright  0DEBUG Goodbye Exit None /None END. ok at VersaForm Text VersaForm /Articles Review: Impressions of /// E-Z Pieces Text ThreeEZ /Articles Author Guidelines Text Authors.Guide /Articles Print Articles on Paper Menu Print.Menu /Articles You CAN Get Back from the Monitor! Menu SetDebug.Menu /Articles A Simple List Manager in Pascal Menu List.Mgr.Menu /Articles Business BASIC: A Utility from 'the Lazy H' Text Util.Doc /Articles Review: A Closer LoMENU Contents Arrival Positional, Alpha BEGIN Welcome Text Welcome /Articles New Products and News Text News /Articles Three Ideas: Handy Tools for Business BASIC Text Three.Ideas /Articles SetDebug: Issue One Rereleased March l985 Dave Lingwood Mike Christensen Marlys Christensen Terri Freeman Bob Huelsdonk Brian Matthews Leon Stucki $62.87 178.73 $1337.32 Commission will not be applied to promotional copies, but will be applied to the sale of back-issues. Authors will be paid at the beginning of the first quarter after the article has been published. kette [143360]) * (copies sold * $1.00) In the first issue, the average article was about 18.5K bytes long. This would break down as follows: If issues sold equal... 0 100 1000 10000 ...then author recieves $50.00 ks; $25 for 2 blocks or under. The added commission is based on the number of characters actually published (text and programs) divided by the number of bytes on the diskette, times $1.00 for every copy sold. (characters published / bytes on the disto all published articles and programs. Other arrangements may be made by discussion with the Editor. Authors receive a flat rate per article plus a commission on each unit sold. The flat rate is currently $50 for an article of greater than two bloc_'); "writeln; "writeln ('Then press'); "writeln (' '); "writeln (' ESCAPE 8 and press RETURN several times.'); "writeln (' '); "writeln; "writeln; "writeln ('To get back to //TDEBUG.TEXT } "setdebug; " "{ messages } "gotoxy (0, 2); "writeln (' SetDebug '); "writeln; "writeln ('To enter the monitor, press'); "writeln (' '); "writeln (' RESET. (Memorize the rest before proceeding...)'); "writeln (' program debug; { written by Brian Matthews }   uses "{ only needed for chaining } "chainstuff;  var "cval : string; procedure setdebug; external;  begin "{ only needed for chaining } "getcval (cval); "setchain (cval); " "{ call code from SEO^ڨڨ R8`" 0 ESCAPE 8 and press RETURN several times.! צ To get back to /// Cheers, pressצ  1 9 8 C G and hitצ   RETURN twice.  n pressצ! 0 ESCAPE 8 and press RETURN several times.! צ To get back to /// Cheers, press  SetDebug To enter the monitor, pressצ 4 RESET. (Memorize the rest before proceeding...) צ The/ Cheers, press'); "writeln (' '); "writeln (' 1 9 8 C G and hit'); "writeln (' '); "writeln (' '); "writeln (' RETURN twice. '); "write (' '); "gotoxy (18, 20); "readln end.  ۮáٚ& ۮ áٚ&٪Pצ: צ what name? آPآšآ ȡآתPRצAddš V$ %Y+Y+YZYZZYZYH ޢÓá"4خ++  ,V Nۨ+á82ܪP00/0000/Í0/003V H ٞ +ڢڢצThe list is now empty.P< 0۪P٨+0Z 2Y+Y+ZáYYZYZZYZYT 0۪P٨+0Zۡڢۓ0 > ݡܢݓ!3Lۡڢۓ0 >0۪P٨+0Y YYY+خ á (ڪP. .á.....V<ب+....../ "Z2  ݡܢá!1 J  š]Press SPACE to continue ܗۗܗÓxب+.+.+.///..Z6ب+....../ "Z2ٞ!      $ ڗ@۳: !"#$_FLISTMGR צError: Record was not found. ٗؗ` צAscending insertš V2"צDescending insertš  V4#צInsert beforeafterצ what? Pr$̀צ4Insert: Before Following Ascending Descending Quit ʀV̀ʀ̀W$InsertšWƀ ʀ ^̀applications. Many application programs need lists where the number of items in the list is not known. The programmer could use an array, but that would mean guessing at the largest number of items that would ever a%'()*+,-./e memory will permit, but the List Manager program is only efficient for shorter lists. Too trivial to be a serious application tool, List Manager was meant to be a foundation for programmers who want to use linked lists in their  A S I M P L E L I S T M A N A G E R I N P A S C A L  Mike Christensen This program provides a way to create, examine, edit, store and retrieve a simple list. Lists can grow to be as large as availablh   4 z N D ` @ :x,F*X,= H*List: צAdd צChange Read צSave View צQuit N ۹Kyq.(i.)`*[ Leave? 3AV,RQ  Oga(*^ڡ *.ƂT+ .. (R(V4. >70)+$,AL( 7"ٗ= H*List: צAdd צChange Read צSave View צQuit N |,View: צ Foreward  Backward צ Ascending צ Descending Count Longest Quit ٗ^ٗHڹ&خRV) צCount: There are  צ entries in list ڗٗz+ צ Longest:  צ characters is the longest ڗٗخ>%9&4!/'*CS"'  -ڗ# .צRead: Get what file? PخRP(צ!Save: Keep under what filename? PADٗ ' Change: צClear צDelete  Exchange צInsert Sort צQuit ڗ 8ڗL۹o replace in exchange? PצExchange+-š+VV &Sort:  Ascending  Descending צQuit ٗٗ!ڹ خخW$ Insert: šWƀ ʀ "#AF  \ʀʀ$3 2!צDelete: Name to be deleted? P++ T`%צ(Exchange: Name tppear in the list. When combined with the dynamic variables supported by Pascal, the simple concept of a linked list provides a useful solution. There are a number of ways to arrange an ordered list in memory. For large lists (perhaps a hundred or more items) where being able to quickly locate certain information is important, separate indexing schemes are often appropriate (B-Tree, binary tree, et.al.). But for smaller lists, simply linking each item to the next is adequate. am Most applications won't need all the functions. In the source, most functions are isolated so you can remove those you don't use. When sorting and viewing, it is important to realize that keys are handled in a case sensitive ma list from disk Save a list on disk View Forward in arrival sequence (first to last) Backward (last to first) Ascending (A to Z) Descending (Z to A) Count of entries within the list Length of longest entry within the list Quit leaves that progr: Add an entry to the list Change Insert before an entry Insert after an entry Insert in ascending order Insert in descending order Exchange an old entry for a new entry Delete an entry Sort Ascending (A to Z) Descending (Z to A) Read a The List Manager program that accompanies this article is menu driven. Just type the first letter of the option you want; RETURN, SPACE, and ESCAPE are used in much the same manner as the p-system. The following functions are supportedred, unlike a static array. Linked lists are very slow for sorting, and very inefficient for searching when the list grows large. Accessing an element at the end of the list may require traversing past all the elements before it. n your programs, it is important to weigh their strengths against their limitations. Linked lists are efficient for inserting and deleting when the list is small. If a linked list is dynamic, then it will use only as much memory as is requi | | | | | | | ------------------------------------------------ | ------------------------------------------------------ If you use linked lists i____ _________ _________ ----->| |---->| |---->| |----- | | Apples | | Bananas | | Oranges | | | ---|________|<----|_________|<----|_________|<-- | | | hed while traversing, the next record is the first. This allows the traversal to begin in the middle of the list and still be able to traverse all records in the list. This closed list is said to be "ring-structured". ____Apples | | Bananas | | Oranges | |________|<----|_________|<----|_________| Another useful enhancement to a linked list is to let the last item point forward to the first item. This way, when the end of the list is reac ability to move back a single element without traversing the entire list. This kind of list may be called a 'double-linked list'. ________ _________ _________ | |---->| |---->| | | on. Operations like inserting and deleting items in the middle of the list are relatively simple to perform. By adding a 'back pointer' to each element, the list can be traversed both forward and back. This is often quite useful, giving theges | |________| |_________| |_________| To see any single item in a linked list, the list must be 'traversed', starting at one end, and moving to the other until the item is encountered. It can only be traversed in one directi Each item can have an embedded pointer to the next item. This is called a 'simple linked list'. ________ _________ _________ | | | | | | | Apples |---->| Bananas |---->| Orannner; thus, uppercase keys will appear ahead of lowercase: Able Baker able baker Note that the simple record DataRecord can be readily modified to accommodate a list with multiple fields, thus making the program more practical. MENU List Manager Arrival Positional, Alpha BEGIN A Simple List Manager in Pascal Text List.Mgr.Doc /Articles Pascal Source for the List Manager Text List.Mgr.Text /Articles Run the List Manager Chain /Articl (Ord (Ch) - ASCIIOffSet); .IF (Ch IN OkaySet) 0THEN Good := True ,END; { IF } (IF NOT Good *THEN Write (Chr (7)) &UNTIL Good; &GetChar := Ch $END; { GetChar } $ "FUNCTION Yes : Boolean; $VAR &Ch, Esc : Char; $BEGIN &Esc := Chr (27); &Ch := .Ch := Chr (Ord (Ch) + ASCIIOffSet); .IF (Ch IN OkaySet) 0THEN Good := True ,END; { IF } ({ If lower case letter was typed, accept upper case substitute } (IF (NOT Good) (AND (Ch IN ['a'..'z']) *THEN ,BEGIN .{ Convert to upper case } .Ch := Chr*-- convert back into Chr (13) } (IF EOLN (Keyboard) *THEN Ch := Chr (13); (Good := (Ch IN OkaySet); ({ If upper case letter was typed, accept lower case substitute } (IF (NOT Good) (AND (Ch IN ['A'..'Z']) *THEN ,BEGIN .{ Convert to lower case }ower -- &e.g. a lower case 'a' is accepted if 'A' is in the OkaySet. } $CONST &ASCIIOffSet = 32; $VAR &Good : Boolean; &Ch : Char; $BEGIN &REPEAT (Read (Keyboard, Ch); ({ Apple Pascal converts carriage returns into spaces, "FUNCTION GetChar "{ PAR } %(OkaySet : SetOfChar) : Char; "{ DESC &Get a character from the user. &The character must be in the set OkaySet. &If it is not, sound the bell and wait for valid character. &OkaySet doesn't need to pass both upper and l Char; "VAR $CVal : String;  "PROCEDURE ClearViewport; "{ DESCRIPTION &Clears viewport on the Apple /// } $VAR &PageCh : Char; $BEGIN &{ Apple /// clear screen } &PageCh := Chr (28); &Unitwrite (1, PageCh, 1, 0, 12) $END; { ClearViewport }   {  List Manager   Written by D. Michael Christensen  (C) 1980, 1984 by Donovan's Reef  Apple /// Pascal   Reference "Programming In Pascal", Grogono, Addison-Wesley, 1979  }   PROGRAM ListMgr; "USES $Chainstuff; "TYPE $SetOfChar = SET OF13456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[2 3 5 6 6K EO^wes/List.Mgr.Code /Articles Quit Exit None /None END. GetChar (['Y', 'N', ' ', Esc]); &Yes := (Ch IN ['Y', ' ']) $END; { Yes }  "PROCEDURE ClearLine; $VAR &LineCh : Char; $BEGIN &LineCh := Chr (30); &Unitwrite (1, LineCh, 1, 0, 12) $END; { ClearLine }  "FUNCTION PageCheck "{ PAR } &(VAR Line : Integer) : Boolean; $VAR &Ch, Cr, Esc : Char; &Continue : Boolean; $BEGIN &Cr := Chr (13); &Esc := Chr (27); &Continue := True; &IF (Line > 20) (THEN *BEGIN ,Gotoxy (0, 22); Write ('Press SPACE to continue '); ,Ch := GetChar ([' '(REPEAT *Loop := Loop^.FrontPointer (UNTIL (Loop = Base) OR (Data.Key > Loop^.Key); (InsertBeforeInRing (Loop^.Key, Base, Data, Discard) &END; { DescendingInsert }  $PROCEDURE DispAscending ${ PAR } '(PortLine : Integer; Base : DataPointer); &VA&END; { DeleteFromRing }  $PROCEDURE DescendingInsert; ${ DESC (Add alphabetically -- insert a record from old ring into sort ring. (Caution: This routine is case sensitive! } &VAR (Loop : DataPointer; (Discard : Boolean; &BEGIN (Loop := Base; (Found := FindInRing (Key, Base, Ref); (IF Found *THEN ,BEGIN .IF Base^.BackPointer = Ref 0THEN Base^.BackPointer := Ref^.BackPointer; .Ref^.FrontPointer^.BackPointer := Ref^.BackPointer; .Ref^.BackPointer^.FrontPointer := Ref^.FrontPointer ,END  1; (UNTIL (Loop = Start); (CountMembers := Count - 1 &END; { CountMembers } $PROCEDURE DeleteFromRing ${ PAR } '(Key : String; (VAR Base : DataPointer; (VAR Found : Boolean); &VAR (Ref, NewPointer : DataPointer; &BEGIN ng }  $FUNCTION CountMembers ${ PAR } '(Base : DataPointer) : Integer; &VAR (Count : Integer; (Loop, Start : DataPointer; &BEGIN (Loop := Base^.FrontPointer; (Start := Loop; (Count := 0; (REPEAT *Loop := Loop^.FrontPointer; *Count := Count +a.Key < Loop^.Key); (InsertBeforeInRing (Loop^.Key, Base, Data, Discard) &END; { AscendingInsert }  $PROCEDURE ClearRing ${ PAR } '(VAR Heap : HeapPointer; (VAR Base : DataPointer); &BEGIN (Release (Heap); (InitRing (Heap, Base) &END; { ClearRi(Add alphabetically -- insert a record from old ring into sort ring. (Caution: this routine is case sensitive! } &VAR (Loop : DataPointer; (Discard : Boolean; &BEGIN (Loop := Base; (REPEAT *Loop := Loop^.FrontPointer (UNTIL (Loop = Base) OR (Daturrent end of ring } ,FrontPointer := Base; ,BackPointer := Base^.BackPointer; ,Base^.BackPointer^.FrontPointer := NewPointer; ,Base^.BackPointer := NewPointer *END &END; { AppendRing }  $PROCEDURE AscendingInsert; ${ DESC er); FORWARD; ( $PROCEDURE AppendRing ${ PAR } '(VAR Base : DataPointer; (Data : DataRecord); &VAR (NewPointer : DataPointer; &BEGIN (New (NewPointer); ({ Add new data } (NewPointer^ := Data; (WITH NewPointer^ DO *BEGIN ,{ Link into ring at cWARD; ( $PROCEDURE InsertBeforeInRing %(Key : String; &VAR Base : DataPointer; &Data : DataRecord; &VAR Found : Boolean); FORWARD; $ $PROCEDURE SortAscending &(VAR Base : DataPointer); FORWARD; & $PROCEDURE SortDescending &(VAR Base : DataPoint&VAR Ref : DataPointer) : Boolean; FORWARD; ' $PROCEDURE InitRing %(VAR Heap : HeapPointer; &VAR Base : DataPointer); FORWARD; $ $PROCEDURE InsertAfterInRing %(Key : String; &VAR Base : DataPointer; &Data : DataRecord; &VAR Found : Boolean); FOR $ $PROCEDURE AscendingInsert %(Base : DataPointer; &Data : DataRecord); FORWARD; $ $PROCEDURE DescendingInsert %(Base : DataPointer; &Data : DataRecord); FORWARD; ( $FUNCTION FindInRing %(KeyToBeFound : String; &Base : DataPointer; = (RECORD *FrontPointer, BackPointer : DataPointer; *Key : String; (END; { Record } &FileOfDataRecord = FILE OF DataRecord; $VAR &Base : DataPointer; &Heap : HeapPointer; &Data : DataRecord; &DataFile : FileOfDataRecord;  ${ Ring operations }, Cr, Esc]); ,IF (Ch IN [' ', Cr]) THEN ClearViewport; ,Line := 2; ,Continue := NOT (Ch = Esc) *END; { IF } &PageCheck := Continue $END; { PageCheck } * "PROCEDURE RingList; $TYPE &HeapPointer = ^Integer; &DataPointer = ^DataRecord; &DataRecordR (TempHeap : HeapPointer; (Loop, Temp : DataPointer; (Continue : Boolean; &BEGIN (Mark (TempHeap); (ClearViewport; (Temp := Base; (SortAscending (Temp); (Loop := Temp^.FrontPointer; (REPEAT *Continue := PageCheck (PortLine); *IF Continue THEN BEGIN ,Gotoxy (0, PortLine); ,Write (Loop^.Key); ,Loop := Loop^.FrontPointer; ,PortLine := PortLine + 1 *END { IF } (UNTIL Loop = Temp; (Release (TempHeap) &END; { DispAscending } $PROCEDURE DispBackward ${ PAR } '(PortLine :art : DataPointer; &BEGIN (Loop := Base^.FrontPointer; (Start := Loop; (Longest := 0; (REPEAT *Loop := Loop^.FrontPointer; *LenBuffer := Length (Loop^.Key); *IF (LenBuffer > Longest) *AND NOT (Loop = Base) ,THEN Longest := LenBuffer (UNTIL (Loop.BackPointer; .Ref^.BackPointer^.FrontPointer := NewPointer; .Ref^.BackPointer := NewPointer ,END &END; { InsertBeforeInRing }  $FUNCTION LengthOfLongestKey ${ PAR } '(Base : DataPointer) : Integer; &VAR (Longest, LenBuffer : Integer; (Loop, St$PROCEDURE InsertBeforeInRing; &VAR (Ref, NewPointer : DataPointer; &BEGIN (Found := FindInRing (Key, Base, Ref); (IF Found *THEN ,BEGIN .New (NewPointer); .NewPointer^ := Data; .NewPointer^.FrontPointer := Ref; .NewPointer^.BackPointer := Ref^nter = Ref 0THEN Base^.BackPointer := NewPointer; .NewPointer^.FrontPointer := Ref^.FrontPointer; .NewPointer^.BackPointer := Ref; .Ref^.FrontPointer^.BackPointer := NewPointer; .Ref^.FrontPointer := NewPointer; ,END &END; { InsertAfterInRing }  e list is now empty.' *END &END; { InitRing }  $PROCEDURE InsertAfterInRing; &VAR (Ref, NewPointer : DataPointer; &BEGIN (Found := FindInRing (Key, Base, Ref); (IF Found *THEN ,BEGIN .New (NewPointer); .NewPointer^ := Data; .IF Base^.BackPoiund) *THEN FindInRing := False *ELSE ,BEGIN .Ref := Loop; .FindInRing := True ,END &END; { FindInRing }  $PROCEDURE InitRing; &BEGIN (Mark (Heap); (New (Base); (WITH Base^ DO *BEGIN ,FrontPointer := Base; ,BackPointer := Base; ,Key := 'Th$FUNCTION FindInRing; &VAR (Loop, Start : DataPointer; &BEGIN (Loop := Base^.FrontPointer; (Start := Loop; (REPEAT *Loop := Loop^.FrontPointer; (UNTIL (Loop^.Key = KeyToBeFound) OR (Loop = Start); (IF (Loop = Start) AND (Loop^.Key <> KeyToBeFoord } &VAR (Ref : DataPointer; &BEGIN (Found := FindInRing (Key, Base, Ref); (IF Found *THEN ,BEGIN .NewData.FrontPointer := Ref^.FrontPointer; .NewData.BackPointer := Ref^.BackPointer; .Ref^ := NewData ,END &END; { ExchangeInRing }  PortLine := PortLine + 1 *END { IF } (UNTIL (Loop = Base) OR NOT (Continue) &END; { DispForward }  $PROCEDURE ExchangeInRing ${ PAR } '(Key : String; (Base : DataPointer; (NewData : DataRecord; (VAR Found : Boolean); ${ DESC (Trade data in recr); &VAR (Loop : DataPointer; (Continue : Boolean; &BEGIN (ClearViewport; (Loop := Base^.FrontPointer; (REPEAT *Continue := PageCheck (PortLine); *IF Continue THEN BEGIN ,Gotoxy (0, PortLine); ,Write (Loop^.Key); ,Loop := Loop^.FrontPointer; ,,Write (Loop^.Key); ,Loop := Loop^.FrontPointer; ,PortLine := PortLine + 1 *END { IF } (UNTIL (Loop = Temp) OR NOT (Continue); (Release (TempHeap) &END; { DispDescending } $PROCEDURE DispForward ${ PAR } '(PortLine : Integer; (Base : DataPointe : DataPointer; (Continue : Boolean; &BEGIN (Mark (TempHeap); (ClearViewport; (Temp := Base; (SortDescending (Temp); (Loop := Temp^.FrontPointer; (REPEAT *Continue := PageCheck (PortLine); *IF Continue THEN BEGIN ,Gotoxy (0, PortLine); op := Loop^.BackPointer; ,PortLine := PortLine + 1 *END; { IF } (UNTIL (Loop = Base) OR NOT (Continue) &END; { DispBackward } $PROCEDURE DispDescending ${ PAR } '(PortLine : Integer; Base : DataPointer); &VAR (TempHeap : HeapPointer; (Loop, Temp Integer; (Base : DataPointer); &VAR (Loop : DataPointer; (Continue : Boolean; &BEGIN (ClearViewport; (Loop := Base^.BackPointer; (REPEAT *Continue := PageCheck (PortLine); *IF Continue THEN BEGIN ,Gotoxy (0, PortLine); ,Write (Loop^.Key); ,Lo = Start); (LengthOfLongestKey := Longest &END; { LengthOfLongestKey } $PROCEDURE ReadRing ${ PAR } '(VAR DataFile : FileOfDataRecord; (VAR Filename : String; (VAR Base : DataPointer); &VAR (Data : DataRecord; &BEGIN (ClearRing (Heap, Base); (Reset (DataFile, Filename); (Data := DataFile^; (AppendRing (Base, Data); (REPEAT *Get (DataFile); *Data := DataFile^; *IF NOT EOF (DataFile) ,THEN AppendRing (Base, Data) (UNTIL EOF (DataFile); (Close (DataFile) &END; { ReadRing } $PROCEDUR); .Readln (RefKey) ,END; { AskWhereToInsert }  *BEGIN { AskInsertInRing } ,Esc := Chr (27); ,REPEAT .Gotoxy (0, 0); .ClearLine; .Write ('Insert: Before Following Ascending Descending Quit '); .Ch := GetChar (['B', 'F', 'A', 'D', 'Q', Esc]); . Base) 2END { IF } ,END; { AskDescendingInsert }  *PROCEDURE AskWhereToInsert *{ PAR } -(VAR RefKey : String); ,BEGIN .Gotoxy (0, 0); .ClearLine; .Write ('Insert '); .IF Before 0THEN Write ('before') 0ELSE Write ('after'); .Write (' what? '*{ PAR } -(VAR Base : DataPointer); ,VAR .Data : DataRecord; ,BEGIN .AskRecord ('Descending insert', Data);  .IF (Length (Data.Key) > 0) 0THEN 2BEGIN 4{ Link into ring } 4DescendingInsert (Base, Data);  4{ Display result } 4DispForward (2,Ascending insert', Data);  .IF (Length (Data.Key) > 0) 0THEN 2BEGIN 4{ Link into ring } 4AscendingInsert (Base, Data);  4{ Display result } 4DispForward (2, Base) 2END { IF } ,END; { AskAscendingInsert }  *PROCEDURE AskDescendingInsert DataPointer); *VAR ,Data : DataRecord; ,Ref : DataPointer; ,RefKey, NewKey : String; ,Before, Found : Boolean; ,Ch, Esc : Char;  *PROCEDURE AskAscendingInsert *{ PAR } -(VAR Base : DataPointer); ,VAR .Data : DataRecord; ,BEGIN .AskRecord (',Ch, Cr, Esc : Char; *BEGIN ,Cr := Chr (13); ,Esc := Chr (27); ,Gotoxy (0, 0); ,ClearLine; ,Write (Chr (7), 'Error: Record was not found. '); ,Ch := GetChar ([Cr, Esc, ' ']) *END; { NotFound }  (PROCEDURE AskInsertInRing ({ PAR } +(VAR Base :0AppendRing (Base, Data); 0{ Display result } 0DispForward (2, Base) .END { IF } (END; { AskAppendRing }  &PROCEDURE AskChange &{ PAR } )(VAR Heap : HeapPointer; *VAR Base : DataPointer); (VAR *Ch, Esc : Char;  (PROCEDURE NotFound; *VAR '' .END { IF } (END; { AskRecord }  &PROCEDURE AskAppendRing &{ PAR } )(VAR Base : DataPointer); (VAR *Data : DataRecord; (BEGIN *AskRecord ('Add', Data); *IF (Length (Data.Key) > 0) ,THEN .BEGIN 0{ Link into ring } (BEGIN *{ Get data here } *Gotoxy (0, 0); *ClearLine; *Write (Title, ': ', Title, ' what name? '); *Readln (Data.Key); *IF Length (Data.Key) > 0 ,THEN .BEGIN 0{ Remove any leading control codes } 0IF Ord (Data.Key [1]) <= 32 2THEN Data.Key := ding } & ${ User interaction routines } ) $PROCEDURE AskRing ${ PAR } '(VAR DateFile : FileOfDateRecord; (VAR Base : DataPointer); &VAR (Leave : Boolean; (Ch : Char;  &PROCEDURE AskRecord &{ PAR } )(Title : String; *VAR Data : DataRecord); (SortHeap : HeapPointer; (Loop : DataPointer; &BEGIN (InitRing (SortHeap, SortBase); (Loop := Base^.FrontPointer; (REPEAT *DescendingInsert (SortBase, Loop^); *Loop := Loop^.FrontPointer (UNTIL (Loop = Base); (Base := SortBase &END; { SortDescen; (Loop := Base^.FrontPointer; (REPEAT *AscendingInsert (SortBase, Loop^); *Loop := Loop^.FrontPointer (UNTIL (Loop = Base); (Base := SortBase &END; { SortAscending } $PROCEDURE SortDescending; &VAR (SortBase : DataPointer; t (DataFile); *Loop := Loop^.FrontPointer (UNTIL Loop = Base; (Close (DataFile, Lock) &END; { SaveRing } $PROCEDURE SortAscending; &VAR (SortBase : DataPointer; (SortHeap : HeapPointer; (Loop : DataPointer; &BEGIN (InitRing (SortHeap, SortBase)E SaveRing ${ PAR } '(VAR DataFile : FileOfDataRecord; (VAR Filename : String; (Base : DataPointer); &VAR (Loop : DataPointer; &BEGIN (Loop := Base^.FrontPointer; (Rewrite (DataFile, Filename); (Get (DataFile); (REPEAT *DataFile^ := Loop^; *PuCASE Ch OF 0'B' : 2BEGIN 4Before := True; 4Gotoxy (0, 0); 4ClearLine; 4AskWhereToInsert (RefKey); 4AskRecord ('Insert', Data); 4IF (Length (Data.Key) > 0) 6THEN 8BEGIN :{ Link into ring } :InsertBeforeInRing (RefKey, Base, Data, Found); :IF Found  0) 6THEN 8BEGIN :{ Link ([Cr, Esc, ' ']) *END; { AskCount }  (PROCEDURE AskLengthOfLongestKey ({ PAR } +(VAR Base : DataPointer); *VAR ,Ch, Cr, Esc : Char; ,N : Integer; *BEGIN { AskLengthOfLongestKey } ,Cr := Chr (13); ,Esc := Chr (27); ,N := LengthOfLongestKey (Bas({ PAR } +(VAR Base : DataPointer); *VAR ,Ch, Cr, Esc : Char; ,N : Integer; *BEGIN ,Cr := Chr (13); ,Esc := Chr (27); ,N := CountMembers (Base); ,Gotoxy (0, 0); ,ClearLine; ,Write ('Count: There are ', N, ' entries in list '); ,Ch := GetChar *Write ('Save: Keep under what filename? '); *Readln (Filename); *{ Save to disk } *SaveRing (DataFile, Filename, Base) (END; { AskSaveRing } &PROCEDURE AskView &{ PAR } )(VAR Base : DataPointer); (VAR *Ch, Esc : Char;  (PROCEDURE AskCount 2, Base) (END; { AskReadRing } &PROCEDURE AskSaveRing &{ PAR } )(VAR DataFile : FileOfDataRecord; *Base : DataPointer); (VAR *Filename : String; (BEGIN *{ Get filename } *Gotoxy (0, 0); *ClearLine; d; *VAR Base : DataPointer); (VAR *Filename : String; (BEGIN *{ Get filename } *Gotoxy (0, 0); *ClearLine; *Write ('Read: Get what file? '); *Readln (Filename); *{ Retrieve } *ReadRing (DataFile, Filename, Base); *{ Display } *DispForward (leteFromRing (Base); 4'E' : AskExchangeRecord (Base); 4'I' : AskInsertInRing (Base); 4'S' : AskSort (Base) 2END { CASE } 0END { IF } *UNTIL (Ch IN ['Q', Esc]) (END; { AskChange } ( &PROCEDURE AskReadRing &{ PAR } )(VAR DataFile : FileOfDataRecor,Write ('Insert '); ,Write ('Sort '); ,Write ('Quit '); ,Ch := GetChar (['C', 'D', 'E', 'I', 'S', 'Q', Esc]); ,IF NOT (Ch IN ['Q', Esc]) .THEN 0BEGIN 2CASE Ch OF 4'C' : 6BEGIN 8ClearRing (Heap, Base); 8DispForward (2, Base) 6END; 4'D' : AskDe, Base) 2END { IF } ,UNTIL (Ch IN ['Q', Esc]) *END; { AskSort } ( (BEGIN { AskChange } *Esc := Chr (27); *REPEAT ,Gotoxy (0, 0); ,ClearLine; ,Write ('Change: '); ,Write ('Clear '); ,Write ('Delete '); ,Write ('Exchange '); .Write ('Ascending '); .Write ('Descending '); .Write ('Quit '); .Ch := GetChar (['A', 'D', 'Q', Esc]); .IF NOT (Ch IN ['Q', Esc]) 0THEN 2BEGIN 4CASE Ch OF 5'A' : SortAscending (Base); 5'D' : SortDescending (Base) 4END; { CASE } 4DispForward (2THEN DispForward (2, Base) 4ELSE NotFound 0END { IF } *END; { AskExchangeRecord }  (PROCEDURE AskSort ({ PAR } +(VAR Base : DataPointer); *VAR ,Ch, Esc : Char; *BEGIN ,Esc := Chr (27); ,REPEAT .Gotoxy (0, 0); .ClearLine; .Write ('Sort: '); ,Gotoxy (0, 0); ,ClearLine; ,Write ('Exchange: Name to replace in exchange? '); ,Readln (Key);  ,AskRecord ('Exchange', Data); ,IF (Length (Data.Key) > 0) .THEN 0BEGIN 2{ Link into ring } 2ExchangeInRing (Key, Base, Data, Found); 2IF Found 4, Found); ,IF Found .THEN DispForward (2, Base) .ELSE NotFound *END; { AskDelete }  (PROCEDURE AskExchangeRecord ({ PAR } +(VAR Base : DataPointer); *VAR ,Key : String; ,Data : DataRecord; ,Found : Boolean; *BEGIN ,{ Get old record key } { AskInsertInRing }  (PROCEDURE AskDeleteFromRing ({ PAR } +(VAR Base : DataPointer); *VAR ,Key : String; ,Found : Boolean; *BEGIN ,Gotoxy (0, 0); ,ClearLine; ,Write ('Delete: Name to be deleted? '); ,Readln (Key); ,DeleteFromRing (Key, Baseinto ring } :InsertAfterInRing (RefKey, Base, Data, Found); :IF Found  '); 0Leave := Yes .END *END { CASE } (UNTIL Leave &END; { AskRing }  $BEGIN { RingList } &InitRing (Heap, Base); &DispForward (2, Base); &AskRing (DataFile, Base) $END; { RingList }  "BEGit '); *Ch := GetChar (['A', 'C', 'R', 'S', 'V', 'Q']); *CASE Ch OF ,'A' : AskAppendRing (Base); ,'C' : AskChange (Heap, Base); ,'R' : AskReadRing (DataFile, Base); ,'S' : AskSaveRing (DataFile, Base); ,'V' : AskView (Base); ,'Q' : .BEGIN *UNTIL (Ch IN ['Q', Esc]) (END; { AskView }  &BEGIN { AskRing } (Leave := False; (REPEAT *Gotoxy (0, 0); *ClearLine; *Write ('List: '); *Write ('Add '); *Write ('Change '); *Write ('Read '); *Write ('Save '); *Write ('View '); *Write ('Qu', Esc]) .THEN 0BEGIN 2CASE Ch OF 4'F' : DispForward (2, Base); 4'B' : DispBackward (2, Base); 4'A' : DispAscending (2, Base); 4'D' : DispDescending (2, Base); 4'C' : AskCount (Base); 4'L' : AskLengthOfLongestKey (Base) 2END { CASE } 0END { IF } is available for earlier ///s, and the Clock/Calendar Kit is $35 (plus installation). NEW TECHNICAL PRODUCTS FROM APPLE FOR DEVELOPERS These should be available now from dealers, or certainly from Apple. They are packaged and priced separately. Apple /// Pascal Tool Kit: Development help for Pascal folks. It includes utilities for such programming functions as compiling, comparing data text files, designing user interfaces, and directory sorting. Apple Pascal Numerics: For both the  S E T D E B U G  Brian Matthews ǠŠ One of the greatest advantages of the Apple ][ was the "openness" of the machine, and one of the key factors in this was thuthor Guidelines Print Authors.Guide /Articles Quit Exit None /None END. Manager Print List.Mgr.Text /Articles A Utility from 'the Lazy H' Print Util.Doc /Articles A Closer Look at VersaForm Print VersaForm /Articles Impressions of /// E-Z Pieces Print ThreeEZ /Articles Adfug.Doc /Articles Assembler Source for SetDebug Print SetDebug.Text /Articles Pascal Source for SetDebug Print Debug.Pas.Text /Articles A Simple List Manager Print List.Mgr.Doc /Articles Pascal Source for ListMENU Print Arrival Positional, Alpha BEGIN Welcome Print Welcome /Articles New Products and News Print News /Articles Handy Tools for Business BASIC Print Three.Ideas /Articles SetDebug Print SetDeb to any program you then load. List price: $95. Available from TAASCOM, 190 Serena Way, Santa Clara, CA 95051 (408) 249-7353. ronic mail connections to other /// users. For more information contact: Mr. Albert Chu Apple Computer Mail Stop 22-A 20525 Mariani Ave. Cupertino, CA 95014 CALENDAR PAK This is a pre-boot module that adds a calendar, calculator and scratch padn CompuServe, and you'll need your own CompuServe account (for which you'll pay, of course). It provides a bulletin board, "electronic newsletter" from Apple on new products and applications, info on software updates, etc. You can also set up elect Device Driver Writer's Guide, A3L0023, $25. 4. Apple /// Technical Reference Manual (not available from dealers). APPLE SERVE /// Apple Computer has created this on-line information service for /// owners, developers and dealers. It is available ograms, reviews, lists of /// software. TECHNICAL MANUALS There are now four such available from Apple Computer: 1. SOS Reference Manuals, V 1&2, (includes ExerSOS Disk), A3L0027, $50. 2. Apple /// Pascal Technical Reference Manual, A3L0006, $50. 3. t full speed, without recompiling. OTHER PUBLICATIONS On Three: Box 3825 Ventura, CA 93006. Monthly, $30/year. Programs, reviews, etc. Open Apple Gazette: Original Apple ///rs Box 813 San Francisco, CA 94101 A users group newsletter. Pro// and ///. Units to give Pascal programmers single, double and extended-precision real and integer numbers. Incorporates IEEE-standard numerics and math functions. Pronto: The Apple /// Pascal Debugger: Debug while executing programs ae existence of the Monitor, that allowed someone to dig around directly in memory. By simply pressing the reset key, you could look at your machine language programs, or the disk operating system, or even the Monitor itself. Of course this wasn'tgijklmnopqrstuvwxyz{|}~. However, the routine consists of one instruction, and RTS, which tells the 6502 to go back to what it was doing earlier. In other words, an NMI doesn't do anything. (This isn't entirely true. Some routines in SOS get executed first, but tit can return to what is was doing before the NMI occurred. After everything is saved, the 6502 starts executing at another prespecified location. This time though, the machine language that gets executed isn't in the Monitor, but in SOS itselfthis case, a reset signal isn't generated, instead an NMI signal is generated. An NMI is a Non-Maskable Interrupt, or just a fancy way of saying an offer that the 6502 can't ignore. When an NMI occurs, the 6502 saves everything, so later when everyone said it couldn't be done, they were right. So now that we have decided it couldn't be done using reset, let's look somewhere else. So far, we've been using Control and reset together. What happens if you just press reset? In you reboot. At this point, things look pretty bleak. By the time you actually get into the Monitor, SOS and the program you were running are pretty messed up, so there is no way to do a reset, get into the Monitor, and then return. It seems that tually part of it. The problem comes though, because by the time you can actually type commands to the Monitor, things are so messed up by the Monitor and its diagnostics that there is really nothing to return to, so you are stuck in the Monitor untiltempts to boot the disk in the built-in drive. However, if the Open Apple key is pressed, the Monitor starts accepting input from the keyboard, and executing commands. This is what is generally known as the "Monitor", though everything is acare pressed, the Monitor sets up some locations in memory for itself, sets the machine state, and performs some diagnostics to make sure the machine is working properly. Then it checks to see if the Open Apple key is pressed. If not, the Monitor attells the 6502 to stop what it's doing, clean up all of its internals, and start executing the machine language program at a certain prespecified place in memory. In the Apple /// this is the diagnostic portion of the Monitor. When Control and reset sk, and technically, what everyone said was correct. To see why, we have to look at what a reset really does. When you hold down the Control key, and press reset, this sends what is known as a reset signal to the 6502 microprocessor. It directly, and change things, but wait, how do I get back to what I was doing?? Well, according to Apple, and most everyone else, it couldn't be done. Once you performed a reset and were in the Monitor, the only way out was to reboot a di Then along came the Apple ///. Sure enough, the /// had a Monitor just like its older brother the Apple ][, and by holding down the Control and Open Apple keys while pressing reset, there you were, in the Monitor. Again you could examine memory the perfect way to do things, you couldn't hit reset with a program running, examine or change things in the Monitor, and then reenter Applesoft and have the program continue running where it left off. You had to restart the program from the beginning.he net result is as if just an RTS was executed.) To prove this to yourself, boot Business Basic or Pascal, and press just the reset key. It appears that nothing happens, but you now know that the 6502 receives an NMI signal, saves everything, executes and RTS, and returns to Basic or Pascal. (This isn't entirely true either. Business Basic version 1.1 and earlier replaced some addresses in SOS with some of its own, so when you pressed reset, the routine that was executed was in Basic, s want to do is to press Escape, and then 8, to clear the screen and set 80 column mode. If you don't, the Monitor won't handle the screen correctly, being it thinks you're in 40 column mode, but the hardware is displaying 80 columns. Everything wil you will be returned to the Pascal command line. In either case, you can now press reset (remember, just reset and not Control reset) and be in the Monitor. Now that we can get into the Monitor and back, what can we do? The first thing you mayed it. Once it has been invoked, type PERFORM SETDEBUG and press return. That's all there is to it. From Pascal type X to execute a program, and tell Pascal to execute .D1/SETDEBUG (or again whatever you've called it). The program will be executed, and return, type 198CG and press return. This starts the second half of the routine executing, that restores things and returns from the NMI. Š To actually perform SETDEBUG, from Basic type INVOKE .D1/SETDEBUG.INV, or whatever you've calln bytes. SETDEBUG simply subtracts seven bytes from this address, and stores the new address in the JMP instruction that jumps to the NMI routine, so you can now enter the Monitor and get back out. To enter it, just press reset by itself. Toand store the new address back in $1911 and $1912 And that is just what SETDEBUG does. It actually gets the address of the NMI RTS from $1904 and $1905, so if SETDEBUG is performed twice, the second time won't reduce the address by another seveying to do, so they placed a routine in SOS to do just what we want. The entry point to the routine is located seven bytes behind the RTS that is normally executed, so we just have to subtract seven from the address normally at $1911 and $1912, gs up for the Monitor, enters the Monitor, and then restores stuff and returns to executing the program that was running before the NMI. In this case, we're lucky because it seems that the good folks at Apple wanted to just the same thing we're trer early enough to set things up for the Monitor though, all the diagnostics end up being executed, and we're no better off than if we had done a reset. Again things start to look bleak. We need a short little bit of machine language that sets thinitor requires the machine to be set up in a certain way, and if we enter at the wrong place, the machine won't be set up, and the Apple will die a miserable death. (Of course the machine itself isn't damaged, but you would have to reboot.) If we ent We can have an NMI execute anywhere by simply storing the address of our routine at $1911 and $1912. The problem now becomes, what do we want to point the NMI at?? The first thought may be somewhere in the Monitor. Unfortunately, the Monigging around, it's possible to find where the address of the NMI routine is. It is part of a JMP instruction at $1910, so the address sits at $1911 and $1912, with the low part of the address in the first location, as is standard with the 6502.d an NMI, it seems that an NMI is what we want to use to get into the Monitor and back. The problem though, is that the address in SOS that points to the routine to be executed points to an RTS. The solution? Why, modify SOS of course. With a little do pressing reset was effectively the same as pressing Control-C. In Business Basic versions 1.2 and later, this isn't done, but it is an interesting concept that we'll come back to shortly.) So now that we know the difference between a reset anl work the way it should, it will just be a little difficult to see. Below is a list of all of the Monitor commands. Perhaps two of the most interesting are the R and W commands. You can actually read in blocks directly off of the disk, something the Apple ][ Monitor can't do. Also, you can now interrupt a running program by entering the Monitor, and then when you return, the program will continue running! This is because the NMI is treated like a subroutine, and the program that was iBLOCK into ADDR2 through ADDR3. BLOCKX Repeat s.Text /Articles Run SetDebug Chain /Articles/Debug.Code /Articles Quit Exit None /None END. O^Ȧwer has come up with a very handy "environment" for the Apple ///. The three components (the Data Base, the Word Processor, the Spread Sheet) are combined with a simulated desktop that is obviously influenced by Lisa. General Observations: Tht pleasant happenings in the Apple /// world for some time. Capturing the user friendly nature of Quickfile ///, improving both functionality and performance, and combining this with word processing and electronic spread sheets, Rupert Lissn  R e v i e w : / / / E - Z P I E C E S  Leon G. Stucki, Ph. D. Product: /// E-Z Pieces From: Haba Systems, Inc. 15154 Stagg Street Van Nuys, California 91405 Synopsis: This is clearly one of the mosd to 0lda 1905 ;(an RTS), and store this in 0sbc #0 ;the NMI JMP instruction. 0sta 1912 ;Unwrap that darn high byte. 0rts ;That's it!!!  0.end ersions.  0lda 1904 ;Grab low byte of NMI vector 0sec ;Make sure that carry's set. 0sbc #7 ;Fall back 7 bytes from the 0sta 1911 ;byte currently pointeo anything. Setdebug ;changes it so when you hit RESET, SOS enters a routine that saves all the ;important stuff, and jumps into the built in monitor. To reenter SOS, do ;a 198CG from the monitor. Known to work through SOS 1.3, and may work ;in higher v0.title "SETDEBUG, point NMI's at SOS' debug routine" 0.list 0.nopatchlist 0.nomacrolist 0.proc setdebug ;Setdebug points SOS' NMI vector at the debug routine in SOS. It normally ;points at an RTS so that hitting RESET doesn't de bulk, if not all of the program, appears to have been coded in assembly language. Thus, the first observation that I had when comparing this system with Quickfile /// is that it is roughly an order of magnitude faster on the hard stuff. Sorting and rearranging within large data sets is very fast (roughly 6 to 10 times faster). Yet even more impressive is the speed improvement for "Finds". Performing finds for large files in Quickfile /// is anything but quick. It is not uncommon to so be some others), has really shown some clever graphic integration concepts. Also, what about a special driver to support spooled I/O while still allowing me to continue with other more useful work than watching the printer run. Recommendation: Thislso wish to reflect upon the integrated graphic, word, and spread sheet capabilities of some of the newer systems on "that other brand" of machines. In particular, a new product called Ovation on "that other brand of machine" (I believe there may alarticular it would be very nice to be able to have a truly integrated graphics and chart-building capability. Mixed text and graphics may be further away, but it sure would be nifty if it could be combined with a truly graphic word processor. I aonvinced the Apple /// is capable of much more than most people think. Wish List: This program wets the appetite in at least two ways. First, the concept of of the Desk Top should be expanded to support other user or commercial programs. In pt program . . . but not quite. It's too bad that no one has yet attempted to use the graphics capabilities of the Apple /// to provide a more visual editor with various fonts. Having seen a prerelease copy of Draw-On /// at SoftCon, I am criver. I finally had to change the internal driver device type number from hex 40 to hex 41 before the printer configuration program would do what I wanted. This should be fixed. The Word Processor is almost a what-you-see- is-what-you-ge slightly slower than the original VisiCalc but is fast enough to be very useful. Problems and Observations: The configurability for various printers is very valuable. However, I wasted quite a little while trying to install my PKASO parallel printer d /// E-Z Pieces Spread Sheet also uses a Quickfile /// type syntax. It appears to be equivalent to the Advanced Version of VisiCalc. It supports very large data models and even allows columns of differing widths. The speed of recalculation seemsities of the Apple ///, I was not able to get ahead of the machine. This is even true for fast scrolls. With Apple Writer /// it is quite easy to get ahead of yourself; this does not seem to be a problem with this system. The Spread Sheet Thea Quickfile /// type command set. Sufficient "Help" messages are provided and allow someone familiar with Quickfile /// to pick up the word processor almost immediately. Here again the speed is very good. Even using the fast repeat capabiltional superset of Quickfile ///. Most of the commands are identical, but the performance has been greatly increased and the capacities of certain functions have also been increased. The Word Processor The /// E-Z Pieces Word Processor uses lmost instantly. One can format diskettes at any time without losing text or time. And with the use of the "Cut and Paste" features it is very easy to copy and rearrange information rapidly. The Data Base The /// E-Z Pieces data base is a funcop In an obvious attempt to create a Lisa-like feel, /// E-Z Pieces supports the Desk Top paradigm. This interface is very effective and pulls together a very powerful set of capabilities. One can, for instance, jump between numerous open files await over a minute to retrieve the proper subset of records. It is also common to get ahead of the system when viewing the selected items. /// E-Z Pieces, on the other hand, performs these same tasks in just a couple of seconds. The Desk T program is a must for any serious user of the Apple ///. The capabilities of the Spread Sheet together with the speed of the Data Base and the additional capabilities of the Word Processor make this a "Best Buy". I'm curious to see what the Haba Folks can come up with next. " bytes. The "count%" variable is returned after a read, containing the number of bytes actually read. The IF test after the read checks that this figure agrees with the number of bytes in the array (though you might want to read fewer bytes i RETURN The RW variable controls reading or writing. DD$ is the important value passed to FILREAD or FILWRITE which tells it the name of the array to find in memory, and from the beginning (0th cell) of which to begin reading or writing "nbytes%afile%,filename$ 1060 IF RW THEN PERFORM FILWRITE(%datafile%,@DD$,%nbytes%) 1070 IF NOT RW THEN PERFORM FILREAD(%datafile%,@DD$,%nbytes%,@count%): IF nbytes%<>count% THEN PRINT "Err: Read file <> array length!" 1080 CLOSE#datafile% 1090e for array read/write. RW=0 to read, =1 to write 1010 PRINT RW$(RW); "ing array DD%" 1020 nbytes%= (N+1) * 2:REM 2 since integer array 1030 datafile%= filenumb: REM # of data file 1040 DD$= "DD%": REM name of array to transfer 1050 OPEN#dat INVOKE"request.inv":REM at beginning of program 20 DIM RW$(1): RW$(0)= "Read": RW$(1)= "Writ" 100 filename$= "dataout": RW= 1: REM write data 110 GOSUB 1000 200 filename$= "datain" RW= 0: REM read data 210 GOSUB 1000 1000 REM Subroutine file name, and the number of bytes to transfer. Let's assume you have an array DIMensioned as DD%(N) which contains data you want to save save to disk, then re-fill with other data on the disk. Here are the appropriate BB statements to do it: 10of numeric data arrays. They are FILREAD and FILWRITE. FILREAD/FILWRITE do a read or save directly into/from a vector. The data are saved as a binary file on the disk. The PERFORM command tells the invokable the name of the array or vector, throm Applesoft to BB. The tricks add back some of the flexibility of Applesoft. 1: ARRAY HANDLING AND REQUEST.INV If you haven't already discovered it, REQUEST.INV, provided with BB, contains very useful routines for rapid reading and saving eatures you need, but it lacks the flexibility provided by the openness of the ][. This article and attached programs recount some of the pitfalls we encountered and useful tricks devised in transferring a large statistical analysis package ftthews Action-Research NW BASIC COMPARISONS Most Apple /// Business BASIC users also work with Applesoft, either from earlier ][ days, or through emulation mode. Business BASIC (hereafter "BB") has all the professional f  T H R E E I D E A S :   H a n d y T o o l s f o r B u s i n e s s B A S I C  Text by Dave Lingwood Programs by Brian Manto an array, you should never read more!). You could even put the names of various arrays into a string array, then assign DD$= ARRAY$(k), for example, or pass the array name into the subroutine as a parameter. There is a lot of flexibility here, and more importantly, a lot of SPEED. Data transfer is much faster than with INPUT/PRINT. 2: DIMENSIONS, STRINGS AND THE 256K /// A problem we bumped into almost immediately with the stat package was the limit on array size, and thnal "back end" of the program, so as not to overwrite the data. Brian began to dig around in memory, and added a lot to what we had previously known about reserved locations in BB. Once he found the statement pointer he was able to che Applesoft zero page locations containing the current line pointer -- in effect the one-line subroutine found its own address in memory. Then the code was BLOADed, the only trick being that each "module" so loaded must be shorter than the origiaster than would have been the case on the ][, but there just wasn't enough disk space for the program! Back to the drawing board. The way overlay worked on the ][ was to find the RAM address of one line of the program: a simple subroutine peeked tNov., 1980). The various command-specific program segments were saved as binary files, then BLOADed quickly right into the middle of the running program, which never knew what had hit it. When we converted to the ///, the improved CHAIN was fat the common code has to exist in each chained program. That means longer read time and disk space wasted through duplication of that code. The ][ version already had solved this by inventing program overlay (see "Call-A.P.P.L.E.," c code that is used depending on which command the user gives. The traditional way to handle this is to have a main "menu" program chain various separate programs depending on which command is given. The trouble with this is thsary? The answer lies in the heart of our program design, and is related to disk space and speed. The AIDA statistical package consists of a chunk of common code that is always used to process commands, read and save data, etc., plus specifi Errlist=16 " errlist+1--errlist+20 " error messages What a classic pain. But, it works. 3: BLOAD+BSAVE+PEEK+POKE = PROGRAM OVERLAY A heck of an equation. Why resurrect these commands we thought the superior design of the /// made unneces the "base" of each list. For example, if we had 10 commands, 6 options, and 20 error codes the master string array would look like this: Cmdlist=0 strings cmdlist+1--cmdlist+10 are commands Optlist=10 " optlist+1--optlist+6 " optionses as the program wades through all the string arrays to find the numerics. The solution we came up with, though a bother, was to dimension just one string array, then store all the various command lists, etc., in it, using an offset variable to find1! Variable/memory error Again, it means you've accessed a string that crosses the block boundary on a 256K machine. It brings the program to a screeching halt! Once you know this, the cure is to dimension all the strings first. Uhg. Speed tumblefine your fast numeric arrays first; but if your numeric array is a full 64K in size, that would force any string arrays subsequently defined to cross into the second 64K block, generating an error that is not mentioned anywhere handy: Error code 2 program design (especially with FILREAD/FILWRITE available). The second causes real speed problems if you have many string arrays. Being an interpreter, BB has to scan through the list of all arrays to find the one you want. Normally you'd de rule about string arrays in BB that affects the 256K machine. Simply put: a. No numeric array can be longer than 64K b. Strings must be defined in the first 64K block of program memory. The first, while a pain, you can live with by carefulreate the needed "overlay" statements by peeking the counter, then BLOADing the module to that point -- after creating PEEK, POKE, BLOAD, and BSAVE, of course! First, let's describe those four new commands, which are useful in their own right. Later we'll combine them into the form needed for program overlay. Peeking and Poking Peek and Poke are defined in the AIDA.TOOLS invokable. Both routines are set up to peek or poke one- or two-byte values, and to use the "extend byte" for the RAM bankw line numbers 800 INPUT "MODULE NAME TO SAVE? ";A$: FILE$= "MODULE."+A$ 810 GOSUB 998: last%=exfn%.peek(%61,%0,%1): len%= last% - addr% 820 PERFORM bsave(@FILE$,%addr%,%xtend%,@len%) 830 PRINT FILE$;" contains ";len%;" bytes.": END 998 addr%=exfn%.peek(%s a BB program. You may choose to write it following the common code to permit testing. When ready to save the module as a binary module, SAVE the program first, then EXEC the following text file: DEL 1, 998: REM Remember, modules can't have these loy other string function, such as MID$, could also be used. In our program, the common code runs through line number 997. Line 998 is the transition subroutine, and code just above that point does the actual overlay. Each proto-module is created atring contents out of the program and into normal string storage. The approach below will do the latter: READ A$: TITLE$= A$ + "" B$= "This is a string" + "" The string concatenation forces BB to move the string out of the program area. Anction statements may be defined there, no ONERR or ONEOF statements may be defined or have their destination there, d) no common or module code may refer to any line number here after the first BLOAD, and c) any string assignments must force the sde will be overwritten with the binary modules successively BLOADed. There are a few other rules for writing the initialization code, based on the obvious fact that those lines of the program aren't going to be there later. They are: a) no funm is first loaded and run, initialization or startup code is contained in the overlay area. This initialization code MUST be longer than any later module will be, even if you must pad it out with long REM statements. Later this initialization con't want to have to re-read (as with chain) extensive common code. The program must be laid out like this: Common code (low line numbers) Transition subroutine (finds its own RAM address) Overlay "module" (high line numbers) When the progra the number of bytes actually read from the disk. All four of these commands are contained in the AIDA.TOOLS invokable described at the end of this article. Overlay This technique lets you break long programs into shorter pieces, where you dobsave(@filename$,%address,%xtend,@savelength%) PERFORM bload(@filename$,%address,%xtend,@foundlength%) Before BSAVE the number of bytes to be written must be stored in the "savelentth%" variable. After a BLOAD this same variable will containssumes that you know WHAT you're poking, and why. The list of zero-page and other BB locations found elsewhere on this disk provides valuable guidance. Bload and Bsave The syntax for these commands, set up as invokables, looks like this: PERFORM e) decimal in the "true" (xtend=0) zero page. The poke will poke the two-byte value contained in "VALUE%" into 61 and 62. This would add 512 bytes onto the BB end of program pointer (clobbering variables, by the way). Careful: poking in particular a VALUE% = value to poke For example: VALUE%= exfn%.peek(%61,%0,%1) VALUE%= VALUE% + 512: PERFORM poke(%61,%0,@VALUE%,%1) The peek will set variable "VALUE%" equal to the two-byte data found at locations 61 (low byte) and 62 (high byts. The syntax is: x = exfn%.peek(%address,%xtend,%length) PERFORM poke(%address,%xtend,@VALUE%,%length) where: address = address of low byte (0-65535) xtend = extend byte (0-3) length = 0 for one-byte, 1 for 2-byte 79,%0,%1): xtend%=exfn%.peek(%5712,%0,%0): addr%=addr%+exfn%.peek(%addr+1,%xtend,0): RETURN RUN Line 998 calculates the address of the line following it. The peek in line 810 reads the BB end of program pointer. Thus, each module will begin at the line following 998, and the length written is (as calculated in variable "len%") the number of bytes between this point and the end of the program. In BB it does not matter (as it emphatically does in Applesoft) that the common code be in memor:num.drives=3:29):60040e3:"Save "+pgm.name$+" w/o utilities to drive #:";:con$;:z$:cof$;z$:z$=13):z$=".D"+z$+"/":60010-60999:z$+pgm.name$fz$+pgm.name$:60000h"/BASIC/UTIL.EX":?j---------------------------------------------10:pgm.name$="UTIL":: 60000 60008< "This is where your program will start when you RUN.":`"a [ [ Utilities - short ] ]bXccon$=5):cof$=6):œ=860005:ۻ=3060006:"ERROR # ";;:" has occurred.": dews was one of the first Seattle /// devotees; and he is, as this disk goes to press (drive?), the proud holder of a new Computer Science B.S. from the U of Washington, which he is applying these days for the benefit of Motorola. computing: a background in English and Journalism, PhD in Communication Research, and R & D work in technology transfer and marketing, before forming his own software/information service company. He is also Secretary of A.P.P.L.E.. Brian Matth have provided AIDA.TOOLS for the individual use of our readers. Commercial rights are, however, reserved. /// /// /// Author Biographies: Dave Lingwood sports the checkered career typical of many in micro space taken up by the program code. Try it. THE AIDA.TOOLS INVOKABLE The assembler source code for AIDA.TOOLS is found in another file on this disk. The ready-to-use invokable is in its own file elsewhere on this disk. Note that the authorspeek(%addr+1,%xtend,0): RETURN When ready to load a module, GOSUB 800 with A$ equal to the last portion of the module file's name. Then GOTO or GOSUB to the code in that module. In use, the modules load very quickly, and of course, there is much less to load in a module is similar to that above: 800 PRINT"LOADING MODULE ";A$: FILE$= "MODULE."+A$ 810 GOSUB 998 820 PERFORM bload(@FILE$,%addr,%xtend,@len%) 830 RETURN 998 addr%=exfn%.peek(%79,%0,%1): xtend%=exfn%.peek(%5712,%0,%0): addr%=addr%+exfn%.he user, BLOAD that module if it is not now present. The module code must follow the rules given for the initialization code, above: no embedded strings that are used outside of the module, and no lines or functions called by other modules. The codeou'll have to keep track of the beginning line numbers of each command, and use an ON k GOSUB ln1,ln2...,ln3 statement. Of course, you also have to know the name of the module in which each command was BSAVEd, and when a command is given by thave been saved. It is a help if the actual execution code in each module begins with the same line number. The common code can then execute that module by a simple GOSUB 1000, or whatever. If more than one command is contained in a module, then yinter in order to find the next line. In Applesoft this pointer contains the absolute address of the next line. The happy result of this design improvement in BB is that you may modify the common code to your heart's content after the modules y, and always of the same length, when the binary files are written or read. This is because BB uses relative next-line pointers. That is, each line of BB code begins with a two-byte pointer containing the number of bytes to add to the line po-------------"t [ [ Utilities ] ]?~----------------------------------------------------------"-----------------------------" [ [ Main menu ] ]"-----------------------------X걜:3:=80:"(p)rint (d)isplay (f)-------" [ [ Init drive ] ]"-----------------------------/"Init drive #:";:60610:=z$:60620:60000060100:z$=13):60000:z$=".D"+z$+"/":"-----------------------------" [ [ Catalog check ] ]"-------------$r#8,"/BASIC/HELP":ž#8#8:60000w#8;zz$:zz$:60535?----------------------------------------------------------" [ [ Filer ] ]?----------------------------------------------------------"------------------------------------------------":" --8 REM [ [ [ ] ] ] ] ] ] ]":" --9 REM-----------------------------|----------------------------":=-4:n"o-----------------------------"p [ [ Help ] ]"q----------------------------------------T"File name? ";fi$:#8,fi$:ž#8#8:60000^#8;zz$:zz$:60510d"e-----------------------------"f [ [ Title lines ] ]"g-----------------------------h" --6 REM":" --7 REM-----------------------------|--0000F?G----------------------------------------------------------"H [ [ Display ] ]?I----------------------------------------------------------"Q-----------------------------"R [ [ Text file ] ]"S--------------" [ [ Text file ] ]"------------------------------"File name? ";fi$:#8,fi$:ž#8#8:60450#8;zz$:#9;zz$:60410"-----------------------------" [ [ Close printing ] ]"!-----------------------------"#9:6references sent to printer (y?):";:60100:z$<>"y"60450B#9;pgm.name$,Ҡ,4)+"/"+Р,2),;2)+27)+"N"+6):=2:=80%i$=".D2/"+pgm.name$:o$=".printer"œ"/BASIC/RENUMBER.INV"xref(@i$,@o$)::60450"-----------------------------int remarks ] ]"-----------------------------+"TEXT:";z$:#9;z$:z$=""60450:60300"-----------------------------" [ [ Print go- refs ] ]"-----------------------------n"Latest version will be read from drive #2 and +27)+"N"+8):" OUTPUT#9:LIST :?#9;pr$:GOTO 60450":=-1:V"W-----------------------------"X [ [ Print catalog ] ]"Y-----------------------------Z#9::#0:60450"-----------------------------" [ [ Pr[ Print outputs ] ]?----------------------------------------------------------"%-----------------------------"& [ [ Print list ] ]"'-----------------------------(pr$=2)+27)+79)v<#9;pgm.name$,Ҡ,4)+"/"+Р,2),;2)60900:60000"-----------------------------" [ [ Input subroutine ] ]"-----------------------------"@"z$=z$)+32)::ۿ?----------------------------------------------------------" [ ello":60000"-----------------------------" [ [ File menu ] ]"-----------------------------"(d)rive (e)xec (s)ave (-)save w/o util (r)enumber (i)nvoke:";:60100:"des-ri",z$)60600,60780,60700,60005,60800,";:60100:"lcrgt",z$)60200,60250,60300,60350,60400:60000"-----------------------------" [ [ Display menu ] ]"-----------------------------d"(t)ext file (l)abel lines (h)elp file:";:60100:"tlh",z$)60500,60520,60530:"Hile:";:60100:"pdf",z$)60050,60060,60070z$=13):ۺ60000"-----------------------------" [ [ Print menu ] ]"-----------------------------#9,".printer":"(l)ist (c)at (r)emarks (g)o refs (t)ext file:---------------------Osum=7:#8,z$:ž#8#8:ct$,34,3))<>sum"CAT BAD!"+7)::"CAT OK.":Zi=13:#8;ct$::i=150:#8;ct$:sum=sum+ct$,10,5)):ct$,3,3)="CAT"ct$::ۂ"-----------------------------" [ [ Save program ] ]"------------------------------"Save "+pgm.name$+" to drive #:";:60610 0œ60750:z$+pgm.name$"Dz$+pgm.name$::60620:60700\N:=30:7);" NEW to this DISK ";:::60740:7);"Error #";;" has occurred.":h"i-----CHEERS disk "magazine." It is about 6k bytes long and is provided in its BASIC form. That is, a normal BASIC program as opposed to the alternative of saving it as an "EXEC" file. It is handy to have it in EXEC form, though. So, I'll show yo of the routine chores in program development. I wrote it so that it could be easily added to an existing BASIC program and easily deleted when you are through with it. The program accompanies this article on this, the first edition of the /// ness with proper zeal by proposing that it leaves my mind free for all sorts of much more valuable stuff such as the latest data on llama importation into the U.S., but I stray from the subject at hand. I wrote a short program which helps do a number not want to clutter up my mind with a lot of routine words that I must remember in order to get certain things accomplished on my Apple ///, and just lazy enough to not want to type ten or fifteen characters when one should do. I will defend my lazi  B U S I N E S S B A S I C :   A U T I L I T Y F R O M T H E L A Z Y H  Bob Huelsdonk I'm lazy. Not ordinary downright "lazy" lazy, mind you, but just lazy enough toV":600006 "/BASIC/READCRT.INV","/BASIC/RENUMBER.INV":600003"/BASIC/READCRT.INV","/BASIC/BGRAF.INV":60000G The END ] ]"-----------------------------~"(r)eadcrt (n)renumber (g)raphics (c)comb r&n (b)oth r&g:";:60100:"rngcb",z$)60910,60920,60930,60940,60950:60000 "/BASIC/READCRT.INV":60000!"/BASIC/RENUMBER.INV":60000"/BASIC/BGRAF.INt:",newinc%i$=".d"+num.drives-1)+"/"+pgm.name$:o$=".d"+num.drives)+"/"+pgm.name$:o$:œ"/BASIC/RENUMBER.INV":reseqnce(@i$,@o$,%oldstart%,%oldend%,%newstart%,%newinc%)::60840o$"-----------------------------" [ [ Invoke starting line number:",oldstart:oldstart%=oldstart-((oldstart>32767)*65536)M" Old ending line number:",oldend:oldend%=oldend-((oldend>32767)*65536)U"New starting line number:",newstart:newstart%=newstart-((newstart>32767)*65536)"New incremennumber ] ]"-----------------------------"/BASIC/ must be in a drive or RENUMBER.INV must be invoked.":"Read from .d";num.drives-1;" & renumber to .d";num.drives;", then load from .d";num.drives;" (y)es?";:60100:z$<>"y"60000U"Old------------------------"j [ [ Make exec ] ]"k-----------------------------ol"File name? ";fi$:#8,fi$:#8:fi$:=0:#8,fi$:" OUTPUT#8:LIST :CLOSE#8:GOTO 60000":=-1:|"}-----------------------------"~ [ [ Reu how to use the utility program to make an EXEC of itself. First, some introduction: For the uninitiated, an "EXEC" file in this case is a BASIC program listing saved as a TEXT file (in this form it uses about 8k bytes on the disk). When this is done, the program segment (just a piece of a total program) can be added to an existing BASIC program by the command: )exec /basic/util.ex This command would cause SOS to look for a diskette named BASIC in one of the configured disk driveskettes to make duplicate back-ups, a habit I would encourage. The line number one shown is to allow normal operation of your program with a "run" command. It would "GOTO" whatever line is the normal first line of your program. There is one more change I type "goto 6" and the utility is automatically restored to the end of the program if my /BASIC/ diskette is in one of the drives. I use three drives, a luxury we don't all have but it can be done nicely with two. However you must change dispace redundantly. Also I wrote very tight and uncommented code to save space. I recently modified the utility so that I can easily delete most of the code and still have a program "save" routine available. Then when I want the full utility available,I first developed this utility, I added it to each program I wrote because I really missed it if it weren't there. After awhile I must have had it stored at least a hundred times on various diskettes and I am too Scotch to use that much s be overwritten. Of course you had a back-up, right? Line 5 is my laziness showing again. I am too lazy to type "goto 60000" and so I only need to type "goto 5." Line 6 is another quick feature with a goal of using less total disk space. When in to avoid usually used line number ranges so when you EXEC the segment on a program you will not be likely to get duplicate line numbers. If you did, the new lines would replace the preexisting lines and part of your previous program wouldhe program you are working on instead of "UTIL." I chose line numbers less than 10 since program listings which might preexist and to which you might want to add this utility usually start with 10. The bulk of the program uses lines above 60000; aganame$="UTIL":RETURN 4 : 5 GOTO 60000 6 GOTO 60008 Lines two and four are not necessary and are just for show to set off the program name. Line 3 is to identify the name of the program. You would substitute the actual name of td at the beginning of your existing program. Four are handy and two are necessary to it's proper operation unless you choose to change this feature. These lines are: 1 GOTO 10:REM Pointer to user program beginning. 2 : 3 pgm. that this disk has a copy of the utility as a BASIC program. That is because the utility has within it a routine to make itself into a TEXT or EXEC file. We shall discuss this a little later. The copy on the disk also has six lines which are addeer case. The system does not care and in the case of program lines, all valid commands are converted to upper case when the program is listed and so you have a convenient check by scanning for lower case variables and upper case commands. I mentioned e entering the lines from the keyboard. Voila! You have added the program segment to your existing program in memory. Note that I purposely show the command line in lower case. The reason for this is that I do all BASIC programming work in lowfile to the console. The file does not display the lines to the screen but does show a ")" prompt for each line read. In doing so, the BASIC interpreter does not know that you have not suddenly become an extremely fast typist and thinks that you ar and would then look for a TEXT file on that diskette named UTIL.EX (this is the name that I use for this utility program: UTIL because it is my utility program, "." for a separator, and EX to denote an EXEC file) and would then write the text to the program you should make to allow it to work properly and that is to identify the number of disk drives on you system. Line 60004 has a variable "num.drives=3" in it. If you only have two drives on your system, edit this line by the usual cursor trace over method to change the number to two. After you have added the utility to your program (or started out with it alone, including line 3), you use it by typing "goto 5" and you will get the followine BASIC operating system. The second printing command "c" will print the catalog listing from the diskette with the presently selected prefix to your printer. The third command "r" will result in a prompt: TEXT: This will allow you to print remarkcharacter and no carriage return is required. If you enter a menu by mistake or accident you only have to input a carriage return to return to the main menu. If you are at the main menu, a carriage return will drop you out of the utility and into thur printer or set pr$="" in line 60200 for now to get you going. If all went well, you now have a printout to follow. Since we have started by using the print commands, lets look at the rest of them. You may have noticed that commands require only one NTER" and sends control characters for an EPSON in conjunction with a PKASO card to give listings with perforation skip over. This is done with the pr$=CHR$(2)+CHR$(27)+CHR$(79) in line 60200. If this gives you trouble, you can change the line for yocommand is than traced over and at the end (just past the 0 in 60450) press carriage return. Congratulations!! You have just used UTIL for the first time. Or did something go wrong. The program does assume a printer driver named ".PRIne space past the "T" in LIST. Then line number ranges can be entered in the form "LIST xxxxx-yyyyy." In this case, you want the whole listing, so you can ignore inputting the line numbers and just trace over the whole thing. The remainder of the he last command line. This lets you to input data into the command. Namely the line range desired. Otherwise you would always print out the entire listing. You perform this entry by using the right arrow (without pressing "ESCAPE") to trace to o shame on you. Now you see why the program name is required and the date and time stamps do wonders for tracing through program development. When you reach this point, the cursor will be positioned over the "O" in OUTPUT at the beginning of tthe last command line is displayed, something was printed to your printer. (The program does assume the printer is on and paper loaded.) The line printed was the program name, the date, and the time of day. If you haven't added a real time clock yet, commands: )load /cheers.1.1/util )goto 5 (p)rint (d)isplay (f)ile:p (l)ist (c)at (r)emarks (g)o refs (t)ext file:l OUTPUT#9:LIST :?#9;PR$:GOTO 60450 You will have noticed that just before e will be discussed. In order to follow the rest of this article, it would be handy if you had a printed listing of the utility. And you may just as well get that listing by using the utility itself. You do this by the followingnformation to the printer, displaying information to the screen, or filing information to diskettes. The second layer gives choice menus for these three activities, and the third layer prompts for information as needed. Some special cases of thesdown. The menus are hierarchical. This is a highfalutin word that means one menu leads to others. There are just three layers in this utility structure. This first command line points to three different kinds of activity. Printing ig prompt: (p)rint (d)isplay (f)ile: It does not matter if you had moved the cursor up to the middle of the screen and had program text all around you because the PRINT CHR$(29) in line 60004 clears the screen from the command line s to the printer if you wish to add comments to a listing, to a catalog listing, or just to make a few notes to remember. This is used by typing a message line just after the TEXT: prompt. The message will show on the screen and can be edited by using the cursor. When you press the carriage return, the message line will be printed to the printer. One thing to watch out for; word wrap-around is not automatic except as your printer will control it so make your lines 80 characters or less. that the help file is on the /BASIC/ diskette in one of the drives and that the file is called "HELP." There are six functions accessible from the filer menu: (d)rive (e)xec (s)ave (-)save w/o util (r)enu is just a TEXT file with the desired information in it. It can be created using any word processor or editor which will produce an ASCII TEXT file. If there is enough interest, I will submit a help file sample for a future issue. The program assumes middle of the long lines. If you are doing a sub-routine label, press carriage return at the vertical bar. The final function on the display menu is (h) to display to the CRT, a help file of information as a handy programming reference. The help filewill get a display of partial line numbers and untitled label lines. You fill in the uncompleted information by tracing over symbols you want and filling in what is needed. If you are doing a main routine, type a hyphen over the vertical bar in thean see the results of this labeling by looking at the listing of this utility program. Lines 60187 to 60189 illustrate a main heading and lines 60197 to 60199 illustrate a sub-routine label. When you use this function from the utility program you at any time to read sections of it. The next function "l" is to help be a little neater in labeling sections of the program. I like to have good looking listings with easily discerned title blocks for both main routines and sub-routines. You c The same rules apply as in the above paragraph referring to the printing of text files. In this case the file will be displayed to the screen. If the file is longer than twenty lines, you may use CONTROL number-pad 7 to halt the file scrollingASIC/NOTES The file will then be printed to your printer. A "d" entered from the main menu will show the display menu: (t)ext file (l)abel lines (h)elp file: The first function "t" will prompt for the name of a text file. ne of your drives to the printer. If the file is on a diskette that does not have the selected prefix, then you either need to change the prefix selection (see how later) or enter the full name of the catalog and file name such as: File name? /Bically invoke this module if it is not already. If you wish to configure this function differently, then you can change the diskette name in line number 60380. The last print function is (t)ext file and allows you to print any text file that is in o two and will list the line references to the printer. You are prompted to proceed by pressing "y." Any other input will abort the function. This function requires that the "RENUMBER.INV" invokable module be invoked. The utility program will automat assumes that you have at least two floppy drives on your system and that the /BASIC/ diskette with the RENUMBER.INV invokable module is in drive one. This function warns the user that it will read the latest version of the program from drive)o-refs" which simplifies the use of the renumber program furnished by Apple to print a list of line references for your program. This is very useful when you are developing a program or to analyze the operation of an existing program. This functionYou can keep entering as many lines as you wish with a carriage return at the end of each. When you are through, a single carriage return at the beginning of the input request will bring you back to the main menu. The next print function is the "(gmber (i)nvoke The first command "d", results in the following prompt: Init drive #: The result of inputting a number is to select the prefix of the diskette in that drive. This is handy if you are going to be working with one drive primarily for awhile or to eliminate having to type in the prefix each time you want to change it. The second command "e" allows you to easily make an EXEC file from any part of your program. You will first see the prompt: File name? You must type in the operates by reading a disk file, performing the renumbering and then saving back to another disk file. This can be done by using two different file names on the same diskette but I chose to use the same file name on two different diskettes. If dd the full utility program whenever you wish with the "goto 6" command and you can save the program with the remaining short portion. The fifth command "r" for renumber is one of the more complex commands. The renumber program supplied from Apple Inc.he fourth command "-" will save just as the previous one did, however before it saves the program it will delete the utility program except for lines 60000-60008. This is to save diskette space as mentioned before. Remember that you can re-aed. After the program has been saved and the directory checked, you will be prompted to save it again (to another diskette). If you chose not to, or if this is your second save anyway, a carriage return will bring you back to the main menu. Tre labeled "CAT" on the catalog listing. If the directory is correct, you will see "CAT OK."; if there has been directory damage as determined by the blocks not tallying, then you will hear the bell and the message "CAT BAD!" will be display a "BAD CAT" I do not save to the second diskette until I have solved the problem. The check is done whenever you select a different drive or when you save a program. It works on all floppy diskettes except those with sub-directories which ach allowed occasional overwriting of files on other files. I believe there are still occasions where this will happen. If you feel confident, you can eliminate this step but it does not take long and I have had it save a lost disk because if I getthen the bell will ring and the following message will appear in inverse: NEW to this DISK After the program has been saved to the disk, the directory is given a rudimentary check. I added this because earlier versions of SOS had bugs whi The program will first delete any copy of the program which is presently on the diskette to reduce fragmentation and to eliminate any possible errors from having reduced the program size. If the program did not previously exist on that diskette, u press "s" you will see the following prompt: Save UTIL to drive #: The program name "UTIL" will be replaced with whatever you have named your program in line three. You then input the number of the drive to which you want the program saved.0-60999" in the command line. Note that you do not save the lines from 1 to 6 as this would write these erroneously to another program that you added this utility to. The third function is selected by "s" for saving your program to disk. After yo finish tracing over the line and your EXEC file will be saved to the diskette. This is the function which allows you to save this utility as an EXEC file. You would enter the name "/basic/util.ex" to the file name prompt and would enter "LIST 6000 :CLOSE#8:GOTO 60000 Again the cursor will appear over the "O" in OUTPUT. You trace over the command line just as you did for program listings, adding the line number range you wish included in your EXEC file as LIST xxxxx-yyyyy. Then file name you wish to use for the EXEC file. You must include the pathname of the diskette if you wish the file to go to other than the diskette with the presently selected prefix. You will then see the following command line: OUTPUT#8:LIST you do not like this approach an examination of lines 60800 to 60840 should help you to rewrite the way you choose. The renumber program is an invokable module and must either have been invoked or must be on a disk named "/BASIC/" in one of the drives. If you have only two drives, you can use a disk named "/BASIC/" that has the "RENUMBER.INV" module on it as one of your development diskettes or you can invoke the module from your basic diskette before you enter the renumber function (you can usonk really DOES raise llamas -- on a Washington coast ranch where he hides out on weekends. The rest of the time he is an engineer in Seattle, and the Vice-President of A.P.P.L.E. ou. If you find additional functions or clever changes, you might submit them to our editorial department for inclusion in a future edition of "/// CHEERS." /// /// /// 򠠠: Bob Huelsd9 File numbers used: 8,9 Memory required: 6k bytes Hardware: Clock, two disk drives, printer Printer driver name: .PRINTER That's it! I hope the program can be of some real use to y command letters "rngcb" in line 60900. If you are not familiar with this technique, it simply uses the command letters in an "ON INSTR"ing function to GOTO the selected line numbers. BASIC line numbers used: 1,3,60000-6999 Inputting the proper letter will invoke the modules as shown presuming that they are on a diskette called "/BASIC/" in a drive. You may change the modules or combinations of them by editing lines 60910 to 60950 and then changing the prompt andutomatically invoke the modules but if you have fewer drives, you may find it convenient to pre-invoke the modules that you might need. After pressing "i" you will see the following prompt: (r)eadcrt (n)renumber (g)raphics (c)omb r&n (b)oth r&g: n save the console copy back to drive 2 and you will have two fresh copies to work from. The last function in the filer section is the "i" for invoke command. This is not greatly needed if you keep the /BASIC/ diskette in one drive and use ON ERR to athree. The program then loads the newly numbered program to the console so that you can inspect it. If some problem has occurred, you can start again from the original in drive two. If all is O.K. and you wish to retain the new copy, then you cabers with existing line numbers the renumbering will stop and an error message will be given. After the lines have been renumbered, you have a copy of the old numbering on the diskette in drive two and a copy of the newly numbered program in drive that you wish to have renumbered. Newstart is the beginning line number that you wish your first new renumbered line to have. Newincrement is the increment which will be used for the renumbered lines. If you have a collision of new line numrenumbering will operate. You do this by entering per the prompts printed just above the variables in the command line. Oldstart is the first line in the existing program that you want to renumber. Oldend is the last line of the existing program e it over just as it stands, you will renumber all lines from 0 to 59999 by increments of 10 and the new first line will be 10. Usually, however, you will not want to renumber the entire program. Thus you can enter and control just how the Oldstart Oldend Newstart Newincrement PERFORM resequence(@i$,o$,%0 ,%59999,%10 ,%10 ):GOTO 60840 As before we use our old trick of tracing over a command with the cursor to adapt it to our needs. If you trace the next described function to assist in the invoking). I will show how the prompts would look if you use three drives: /BASIC/ must be in a drive or RENUMBER.INV must be invoked. Read from drive .d2 & renumber to .d3, then load from .d3 (y)es?  V E R S A F O R M : A C l o s e r L o o k  Terri Freeman We Apple /// owners have waited patiently for the fulfillment of Apple's promise of software for the Apple ///. We always have heard that ts $3.00. This week you have a sale on that item, and it's $2.29. You don't have to change the error checking built into your form, just enter the price manually. Probably the most unique and attractive error checking routine is the lookup tables. Youant, that can be entered automatically. In each of the automatically entered items, the entry can be overridden by a manual entry, which is important to allow data entry flexibility. Suppose the price of an item on your form is almost alwaya must be numeric, whether the entry must be one of a set of acceptable answers, what a minimum or maximum allowable entry is. Like VisiCalc, an entry may be calculated from one or more other entries. If the entry is usually some specific const: it has a set of checking routines to look for errors in data entry. So when the form is filled in, the checking routines make sure that most of the data is correct. These routines include left- and right- justification, whether the dat 3. Report (analyze data) 4. Design a print format (for printing individual forms) 5. Copy or print forms ҠǠӠҠԠ VersaForm is designed to emulate your present forms, and it goes one step furtherletely new program. This is the best way to learn what a program can do when you start using your own data. Š͠ These are the five functions of the VersaForm program. 1. Design a Form (screen layout) 2. Filing (enter data) r how to use VersaForm, so you don't have to actually set up a new form just to learn how to use the program. Sample files that have been set up help the user become familiar with VersaForm's functions, without having enter their own data into a compecords that are printed to fit your software -- you can use the invoices you have right now, or design your own! The first thing you do is design a form, or set up the screen layout. But VersaForm provides a step-by-step tutorial to teach the new useuse. ͠Ӡ You set up VersaForm to suit your own forms; you customize a screen layout for entering data in the way you want, and you customize the output or form printing in the way you want. You no longer have to buy invoices or rforms you already use -- the invoices, purchase orders, personnel or client records -- any form you use now can be reproduced into the VersaForm format. You fill in the form on the screen, and VersaForm will print out directly onto the same forms you have a program that does a superior job of such applications as invoicing, client billing, and inventory control. Its performance is more than worthy of the Apple ///'s capabilities. VersaForm is advertised as the database that fits itself to the  he Apple /// is a powerful, wonderful machine whose potential had barely been tapped; but a user practically had to be a programmer to realize this wonderful potential. Now, with the production of VersaForm, by Applied Software Technology, we enter a two part list, consisting of the entry to look for in one data item, and the corresponding entry to insert in another data item. We use the lookup tables to fetch the price of an item, the description of an item, and the name and address of a customer. You are allowed up to 99 entries for each data item. Although this may seem a strict limitation, in many applications the lookup function is invaluable, and the 99 item restriction is quite ample. Suppose you have 500 or 600 differa from the records you select, sorting, totalling numeric data and other features. It will even include a second file (of the same form) in the same report. The report can be printed to the printer, to the screen, or to a Pascal text file which therint" function. ǠӺӠĠԠ There are three ways the VersaForm program can print records: with a report, using a print format, and without using a print format (a copy of the screen). The report prints out dat to access using this key. When you print a report to analyze your data you can select records according to any of your data items, not just the key item. You can also print records like an invoice based on any of the data items, using the "Copy/P number. You can access a form to change it only by calling it up by its key entry. The key may be made up of one data item, or two data items combined. This is a very limited method of access, but you can organize your records so they are easy Ǡ Once a form is designed, you use the "Filing" function to enter records into the file. When the records are saved, they are indexed according to the entry in their "Key" data item -- for example, in a invoice it might be the invoicelittle awkward to go in and change the error checking routines on a file. You have to go through each data item sequentially until you reach the item or items you wish to change. This can be minimized by careful planning when you first design the form.nnot easily change the length of a data item -- the length of a data item must be the same in the old file and in the new file. To change the error checking routines you must go back into the design function of the program. One caution: it is a ple, records from the old file can be copied into the new file (to which the data item "Company" has been added) by using the "Copy by Name" program which comes with VersaForm. That means you can add or delete data items at any time. However, you ca Name Company Address Name City/State/Zip Address Phone City/State/Zip Phone In the above exams from one form into a new form, matching the names of the data items. First you have to design the new form, then copy the records into it. Old File New File -------- -------- you want, using the "Design" function. Those routines can be changed at any time, but the screen layout can't be changed after you have entered records. There is a special copy program which comes with VersaForm, however, that allows you to copy recordto look up values based on the Type of Transaction. This makes VersaForm different from almost any other database available. Ǡ First you design the screen layout of a form, then for each item you enter the error checking routines, and the like. Furthermore, for those customers not included on the lookup table, you could enter the Type of Transaction from the keyboard. VersaForm will automatically fill in the Discount % and calculate prices, for instance, if you have set it up ent customers -- you can enter the 99 names you sell to most frequently. That means that for those 99 customers, VersaForm can automatically fill in such data items as Account Number, Address, Type of Transaction (Wholesale or Retail), Discount % user can edit separately. The report rules the user chooses are always saved to be used again later. If you want the program to ask you for the selection data at the time of printing -- for example, beginning or ending date -- you can include in the rules a variable which the program asks for at the time of printing. When you want to print a single record on a invoice, for example, you design a print format. Once you have designed a print format, you can print a record right after you haveitems. This is best illustrated by an example. ============================================================= INVOICE Inv # ........ Date ........ Sold To: ......................... ......................... .......................have to go back to the menu. Š͠ԺŠӠĠǠ On the other hand, VersaForm offers a unusual feature which I haven't seen in any other data base program. The form can be made up of both "Single" items and "Column" ort" function is pretty clumsy to use: you cannot abort the report once you start, and you have to go back to the menu just to print another report, which takes time. That's also true of the "Filing" function: if you want to change files you rent date at the beginning of the "Filing" function, and the beginning of the "Report" function. It would be convenient if it asked the current date, then remembered it or defaulted to that date when it next needed that information. Also, the "Rep upgrades are available at minimal cost ($20 to $30). Overall performance of the program is quite good, although there are some minor irritating aspects of using it that can be tiresome. For instance, the program always asks you to enter the curt that $10 to $15 expense many companies charge on "locked programs". The manuals are the best written manuals I've seen -- one reference manual, one tutorial manual, and a hard disk installation manual. Company support has been excellent;t change the data with those disks. When you have the hard disk version, you can still use it as a floppy version. All the program disks are completely copyable, and that means you can backup and restore your program disks quickly and withoutry, you can give someone the "Filing" disk and a data disk only, and they cannot print a report with those disks. Similarly, for printing reports, you can give someone the "Report" disk, a report work disk (blank) and a data disk only, and they cannofferent function. Although the floppy disk version may be awkward since the user must reboot when changing disks, the advantage of the floppy version is that you can use it to provide security of your data against unauthorized access. For data en can use all 5 functions from one main menu. In the floppy disk version, each function is treated as a different program, and the user must reboot the computer (with control-reset) (with the boot disk and then the individual program disk) to use a di putting it on a hard disk drive, like the Profile, is the only efficient way to use it. It's true that VersaForm is very easily used on the Profile or similar hard disk. With the hard disk version, the programs load more quickly, and the userinting a copy of a record as it appears in the screen layout. You can make a complete printout of a record quickly for reference with this option. ҠӠƠŠ The program comes on eight disks, and it's often been recommended thatou wish), and your printout can be tailored exactly to the form you have. Comments to be printed on the form can be included as well. The third way VersaForm prints a record is without using a print format -- the program has the option of pr entered it, in the "Filing" function. In our invoice example, you would enter the invoice, save the record, the print it out to send with a delivery. You set up rules to tell the program exactly where to print the data, each item more than once (if y.. Quantity Stock # Description Price Extension -------- ------- ----------- ----- --------- TOTAL $ Thank you. =============================================================== The data items such as Inv #, Date, Sold to, appear only once on each invoice. However, the data items such as Quantity, Stock#, Description, etc. are repeating items, and may appear more than once on each invoice. There may be only one occst dedicated accounting packages written for the Apple /// (or for the IBM PC, for that matter). And for the cost of this database, you can use it for all kinds of record keeping, which allows you to cut the cost per use. That's not true for an de member of the library of available Apple /// software. VersaForm sells for $495; the Pascal Interface Routines sell for $249. Although the cost represents a substantial investment in software, it's comparable to or lower than the cost of mo use the Pascal Interface Routines. The Apple /// is a powerful machine, and we've been waiting a long time for a program that matches the power and capability of this machine. This database is a versatile, yet user friendly program, and a superb . The Apple ///'s built-in numeric keypad offers convenient numeric data entry. Finally, the extra memory of the 256K Apple /// allows greater flexibility -- you can create larger forms then on other machines, and there are added benefits when youainbow, and some versions of the Atari and the TI, there are several nice features available when you use the Apple /// version. First, the Apple /// and the Profile work so well together, and VersaForm does work best when a hard disk is availableout this ability, you have to settle for just the report features which the original programmer wrote. ӠΠŠŠ While VersaForm is available for many other microcomputers, such as the Apple II+ and IIe, the IBM PC, the DEC Ru can also print custom reports from VersaForm files, and do other applications involving VersaForm data. This is a very strong advantage for using VersaForm, because accessibility to data makes a "general" program easy to customize. Withg VersaForm data; a moderately experienced Pascal programmer can access all VersaForm files. You can write a program to read data from a text file into a VersaForm file, or you can read data from one VersaForm file into another VersaForm file. Yoelf, but VersaForm designed them to shortcut setup time for the new user. ŠӠϠӠŠŠ VersaForm also offers a set of Interface Routines, which are available separately. These routines are the key to accessin Because this could discourage the new user, VersaForm now has available sample application templates, similar to the templates available for VisiCalc and similar programs. These ready-made applications are the same forms you could set up yoursthe program requires planning and a little time to set up your applications. The initial setup time varies with the complexity of your application; VersaForm suggests from between 1/2 hour to 2 hours, depending on the particular application. s model of single and repeating items adapts itself excellently to any number of applications -- among them payroll records (print W-2s), client/medical records (print insurance claims and billing), and expense journals. You'll find that This has been a big problem with most data base programs on the market, which can handle mailing lists easily, but cannot satisfactorily handle an invoice with its variable length or repeating items. VersaForm is perfect for this application. Thiurrence of a repeating item, if only one stock item has been sold to this customer, or there may be 5 or 10 or 20 occurrences of a repeating item. VersaForm allows the user to have as many or as few entries in repeating items as each record needs.dicated accounting package. Although the step-by-step tutorial provided by VersaForm is an excellent introduction to the program, next we'll do a hands-on tutorial of sample applications for VersaForm. Finally, we'll learn to use the VersaForm Pascal Interface routines. The uses of this program are limited, as the old phrase goes, only by your imagination! /// /// /// 򠠠 Terri Freeman is Office Manager for Freeman Co. Green/// Cheers program disk, and an articles disk for each of the four issues. rated by Mike and Marlys Christensen. Subscriptions may be ordered by mail or phone; Donovan's Reef 12513 SE 216th Kent, WA 98031 (206) 630-2343 9:00 am to 4:00 pm weekdays The cost of four issues is $40.00, and includes a boot disk, a h minor changes. The magazine was originally intended to be a quarterly publication, but the first four issues will be released about every two months, in an effort to better meet the expectations of 1984 subscribers. Donovan's Reef is owned and opee Author Guidelines for details. /// Cheers was first published by Apple Puget Sound Program Library Exchange Cooperative in the summer of l984, and will continue to be distributed by them. That first issue is being republished by Donovan's Reef witthat /// Cheers will be informative, stimulating, and fun for all of us. We don't want to see Apple ///s dying in dark corners, but being useful and enjoyed. We encourage you to submit your articles, reviews, and programs for possible publication. Seecome involved enough to mention, by learning AppleFile ///, AppleWriter ///, and Multiplan, and am having a lot of fun with them. Between the two of us then, and with quality articles contributed from subscribers and other interested people, we hope heers we fall into both of those categories. Mike has spent innumerable hours (often in the dead of night) digging through the trenches of Pascal, coding, compiling, and more coding; redesigning, debugging, and yes, more coding. I have only recently bof people who like their ///s as much as we do, and who want to get as much from them as possible. /// Cheers will be a forum for sharing information on a variety of topics for experienced programmers and beginning users as well. As editors of /// C  W E L C O M E T O / / / C H E E R S  by Marlys Christensen Welcome to /// Cheers! We are a diskette based magazine dedicated to the Apple /// exclusively. We believe there are lots /, primarily in Pascal, but also in Business Basic. houses in Bothell, Washington. She computerized the business operations using the Apple /// in January 1981, and recently taught two successful tutorials for the Apple to School Administrators. She is also a programmer on the Apple //