Self Doc Scripts
Volume Number: | | 6
|
Issue Number: | | 6
|
Column Tag: | | HyperChat
|
Almost Self Documenting Scripts
By Tom Trinko, Ph.D., Colleen Trino, Fremont, CA
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
Almost Self Documenting HyperTalk Scripts
[Tom Trinko, Ph.D., is working on advanced computing systems designs at LMSC. He has worked on machines ranging from PDP-11s to CDC 6600s. Colleen Trinko is currently a full-time mother and a part-time instructional designer at Apple.]
One problem we all have with code is remembering what it does and how it does it. If most of the code we generated were one shot throw-away stuff this wouldnt be a concern. As the sheer amount of code we have to write increases, however, it becomes more and more difficult to start from scratch. A number of different ways of dealing with the problem have been developed for Mac programmers, such as Prototyper or Program Extender . The simplest technique is still recycling your own code. After all, you at least know youll eventually be able to figure it out!
HyperTalk is well structured to support code reuseabilty because of its pseudo object oriented construction. Once youve written a handler (on mouseup...end mouseup) to perform a certain function, say make visible all of the hidden objects in a card, theres no reason you cant use that same handler in all of your other stacks--or projects if youre using SuperCard. The difficulty we encounter is trying to remember which handlers are where and when you find a handler, remembering what it does.
One possible solution to this problem that weve developed is to place a description of what each handler does at the beginning of the handler. You could place it at the end of the handler to save a little processing time when the handler is executed but HyperCard really doesnt take much time to skip over comments. An example handler is shown in Listing 1.
Listing 1:
function linechoosen loc
--input x,y location of the mouse click(use the clickloc)
--output line number clicked on in a scrolling field
-- This is a generally useful function which takes the clickloc as
-- input and finds out which line in a scrolling field was clicked
on
put second item of loc into pos
put pos - the top of the target into pos
put pos + the scroll of the target into pos
get the textsize of the target
put it+trunc(it/3) into size
put trunc(pos/size)+1 into linehit
return linehit
end linechoosen
Now this is nice but it still doesnt help you find where in your 5 Megabyte stack the handler you want resides. Thats where the scripts in Listing 2 come in handy. These scripts, when placed in a button, will do the following: (1) search through a stack and find all of the functions (function name...end name) and all of the handlers that reside in the stack, and (2) place the location, name and all comments that lie between the first line of the handler/function and the first executable line in the handler/function into two variables functionlist and handlerlist. These two variables are structured as shown in Figure 1. You can place the two variables in two scrolling fields for easy access.
Figure 1
STACK:linechoosen <-- address and name of function line 1 of functionlist
--input x,y location of the mouse click(use the clickloc)
2
-- This is a generally useful function which takes the clickloc
3
-- input and finds out which line in a scrolling field was clicked
4
BKGND handler lists:findloc
5
If you have a really big stack it might still take awhile to wade through all of the information returned so weve included two scripts, shown in Listings 3a and 3b, which let you search for keywords in the two fields and automatically scroll the fields to the line where the match was found. Once youve found the item of interest you can go directly to the script containing the handler if you put the scripts from listing 6 into the scrolling fields and the scripts from listing 5 into the background or card the scrolling field is in. Just click on the line containing the name of the handler or function youre interested in and you will be taken to the edit window for that script.
Listing 4 contains some miscellaneous scripts that are required in the stack script. Note that the closestack, checkfreespace, and deletefield handlers are not required but they make life a little nicer. The redolist and popmenu handlers are used by the scripts which search through the stack and make the function and handler lists. The linechoosen function is used by the scripts in listing 6 but it can be used in any scrolling field to see what physical line the user has clicked on.
If you decide to use the scripts in listing 5 and 6, you must make sure that the address line for a function or handler does not occupy more than one physical line in the field you display it in. The distinction between physical and logical lines in a field is fairly simple. As an example suppose that line n of a variable is 80 characters long. If we put that variable into a field which is 60 characters wide then the logical line n of the variable will be shown on physical lines n and n+1. If however you use the fragment
put line n of field x into temp
Temp will contain the full 80 characters. The reason this is important is that the scripts in listing 4 determine which physical line the mouse has been clicked in and then get that logical line to parse to find the identity of the object containing the script. So if logical line 8 occupies two physical lines then when the user clicks on physical line 24 he will get the wrong answer because logical line 23, not 24, actually contains what is displayed in line 24. This problem is avoided by clipping the length of the logical lines to be less than the physical field width. In the listings the field is assumed to be 75 characters wide. When you set up your card, check that your field is at least this wide. Note that you will not be able to access scripts whose address lines have been truncated because they will not have complete address information. On the other hand you will still be able to access the scripts of handlers displayed after those with names greater than 75 characters in length.
To give you a feel for what a card using these scripts could look like weve included a picture of the version we use in Figure 2.
Figure 2
Weve used a popup menu XFCN as an interface in these scripts because it works well and is an unobtrusive interface. If you dont have access to any of the several popup menu XFCNs presently available you have a couple of simple options. You could add a few checkbox buttons to your card which correspond to the popup menu options, or implement a completely HyperTalk-based popup menu using the techniques described in the MacTutor article Pop-up Menus in HyperTalk by Joseph Bergin(Vol 4 #5 May 1988 pp85). Notice since the popup menu is always accessed through the function popmenu(in listing 4) you only have to change the call to the XFCN there in order to eliminate the need for the XFCN in the stack.
Please send any comments, bug reports, insults, etc. to
GEnie T.Trinko
AppleLink Trinko
MouseHole TomT
Delphi TTrinko
AppleLink Personal Edition Trinko2
Listing 2: This goes into a button labeled Update
on mousedown
--This a mousedown handler so that the popupmenu can be used to let
-- the user pick whether or not the listing should contain the
-- comment lines immediately following the start of handlers and/or
-- functions.
global linesofcode --contains the number of lines of HyperTalk
global temphandlerlist,tempfunctionlist
put 0 into linesofcode
--set up the popupmenu
put With comments,Without comments into list
put the clickloc into loc
--here we let the user decide whether or not comments will be displayed
put popmenu(list,loc) into commentflag
-- if the user cancels then exit
if commentflag < 1 then exit mousedown
--let the user know something is happening
set the cursor to watch
--clear the variables which will contain the lists
put empty into handlerlist
put empty into functionlist
--start by looking throught the stack script
put the script of this stack into temp
add the number of lines in temp to linesofcode
--get a list of the handlers & functions in the stack script
findhandler temp,STACK,commentflag
put temphandlerlist into handlerlist
put tempfunctionlist into functionlist
--here go through all of the background scripts
repeat with i=1 to the number of backgrounds
put the script of bkgnd i into temp
--check to see if there is a script present in the bkgnd
if temp is not empty then
add the number of lines in temp to linesofcode
--here set up the address
put BKGND & the short name of bkgnd i into name
findhandler temp,name,commentflag
put appendlist(temphandlerlist,handlerlist) into handlerlist
put appendlist(tempfunctionlist,functionlist) into functionlist
end if
end repeat
--here go through all of the card scripts
repeat with i=1 to the number of cards
put the script of card i into temp
if temp is not empty then
add the number of lines in temp to linesofcode
put CARD & the short name of card i into name
findhandler temp,name,commentflag
put appendlist(temphandlerlist,handlerlist) into handlerlist
put appendlist(tempfunctionlist,functionlist) into functionlist
end if
end repeat
--Now we have to actually go to the bkgnds and cards in order to
-- access the object scripts. By locking the screen we cut the
-- processing time.
set the lockscreen to true
--Since we know we want to come back to this card pushing the
-- card will save us a little time.
push card
repeat with j=1 to the number of backgrounds
go to bkgnd j
if the number of bkgnd fields > 0 then
repeat with i=1 to the number of bkgnd fields
put the script of bkgnd field i into temp
if temp is not empty then
add the number of lines in temp to linesofcode
put BKGND into name
put the short name of bkgnd j after name
put :FIELD after name
put the short name of bkgnd field i after name
findhandler temp,name,commentflag
put appendlist(temphandlerlist,handlerlist) into handlerlist
put appendlist(tempfunctionlist,functionlist) into functionlist
end if
end repeat
end if
if the number of bkgnd buttons > 0 then
repeat with i=1 to the number of bkgnd buttons
put the script of bkgnd button i into temp
if temp is not empty then
add the number of lines in temp to linesofcode
put BKGND into name
put the short name of bkgnd j after name
put :BUTTON after name
put the short name of bkgnd button i after name
findhandler temp,name,commentflag
put appendlist(temphandlerlist,handlerlist) into handlerlist
put appendlist(tempfunctionlist,functionlist) into functionlist
end if
end repeat
end if
end repeat
repeat with j=1 to the number of cards
go to card j
if (commentflag < 3) or ((the short name of this bkgnd) is not model
space) then
if the number of card fields > 0 then
repeat with i=1 to the number of card fields
put the script of card field i into temp
if temp is not empty then
add the number of lines in temp to linesofcode
put CARD into name
put the short name of card j after name
put :FIELD after name
put the short name of card field i after name
findhandler temp,name,commentflag
put appendlist(temphandlerlist,handlerlist) into handlerlist
put appendlist(tempfunctionlist,functionlist) into functionlist
end if
end repeat
end if
end if
if the number of card buttons > 0 then
repeat with i=1 to the number of card buttons
put the script of card button i into temp
if temp is not empty then
add the number of lines in temp to linesofcode
put CARD into name
put the short name of card j after name
put :BUTTON after name
put the short name of card button i after name
findhandler temp,name,commentflag
put appendlist(temphandlerlist,handlerlist) into handlerlist
put appendlist(tempfunctionlist,functionlist) into functionlist
end if
end repeat
end if
end repeat
--here we return to the original card
pop card
set the scroll of bkgnd field handler to 0
put handlerlist into bkgnd field handler
set the scroll of bkgnd field functions to 0
put functionlist into bkgnd field functions
put This stack contains & linesofcode & lines of code(including
comments) into message
end mousedown
on findhandler temp,source,commentflag
--inputs a script,an address,< 2 --> list comments
--output a list containing the first line of each handler and if
-- commentflag <2 the comment lines imediately following the
-- first line in the handler in global variable temphandlerlist
-- a list of the functions in global variable tempfunctionlist
global temphandlerlist,tempfunctionlist
put 0 into numhandlers
put 0 into numfunctions
put empty into temphandlerlist
put empty into tempfunctionlist
repeat with i=1 to the number of lines in temp
--check to see if the lines starts a handler
if word 1 of line i of temp is on then
add 1 to numhandlers
put source & : &word 2 of line i of temp into temp1
--make sure the line fits in the field. NOTE: if you change
-- the field width and/or font you should change the 75 to the
-- appropriate value. This is necessary because if the address
of a function or
-- handler takes up more than one line in the field it will cause
problems.
put char 1 to 75 of temp1 into line numhandlers of temphandlerlist
--if the user wants the comments listed
if commentflag < 2 then
--a repeat forever might be faster but you could get an error
if
-- a script ended with an unfinished handler
repeat with k=1 to the number of lines in temp
if char 3 of line i+k of temp is - then
add 1 to numhandlers
put char 1 to 75 of line i+k of temp into line numhandlers
of temphandlerlist
else
exit repeat
end if
end repeat
end if
-- here we deal with the functions
else if word 1 of line i of temp is function then
add 1 to numfunctions
put source & : & word 2 of line i of temp into temp1
--if you change the width of the field or the font size you
-- should change the number 75 as well
put char 1 to 75 of temp1 into line numfunctions of tempfunctionlist
if commentflag < 2 then
repeat with k=1 to 100
if char 3 of line i+k of temp is - then
add 1 to numfunctions
put char 1 to 75 of line i+k of temp into line numfunctions
of tempfunctionlist
else
exit repeat
end if
end repeat
end if
end if
end repeat
end findhandler
function appendlist newlist,oldlist
-- input list 1,list 2(both lists are line ordered)
-- output list 2 with list 1 appended afterwards
put the number of lines in newlist into nlines
put the number of lines in oldlist into start
repeat with i=start+1 to (start+nlines)
put line i-start of newlist into line i of oldlist
end repeat
return oldlist
end appendlist
Listing 3a: This belongs in a button labeled Find String
on mousedown
--This is a mousedown handler to allow the use of the popup menu
global lastfoundline,lastfoundchar,findstring,commentflag,fieldname
put Handlers,Functions,Both,Cancel into list
put the clickloc into loc
put popmenu(list,loc) into commentflag
--exit if the user didnt make a selection or he selected Cancel
if commentflag < 1 or commentflag = the number of items in list¬
then exit mousedown
put empty into lastfoundline
put empty into lastfoundchar
put empty into findstring
ask Enter the string to find
--Continue only if the user didnt cancel or not enter a string
if it is not empty then
put it into findstring
-- let the user know something is going on
set the cursor to watch
if commentflag = 1 or commentflag =3 then
put bkgnd field handler into temp1
put findloc(temp1,findstring) into temp2
--if a match was found
if temp2 is not empty then
put item 1 of temp2 into lastfoundline
put item 2 of temp2 into lastfoundchar
--scroll the field to show the match
put getscrollline(lastfoundline,bkgnd field handler) into scroll
set the scroll of bkgnd field handler to scroll
--select the line with the match so it appears in inverse
select line lastfoundline of bkgnd field handler
put handler into fieldname
exit mousedown
end if
end if
--if the user wanted only functions or both searched
if commentflag > 1 then
put bkgnd field functions into temp1
put findloc(temp1,findstring) into temp2
if temp2 is not empty then
put item 1 of temp2 into lastfoundline
put item 2 of temp2 into lastfoundchar
put getscrollline(lastfoundline,bkgnd field functions) into
scroll
set the scroll of bkgnd field functions to scroll
select line lastfoundline of bkgnd field functions
put functions into fieldname
exit mousedown
end if
end if
end if
put Sorry the string was not found into message
end mousedown
Listing 3b: This goes in a button labeled Find Next
on mouseUp
global lastfoundline,lastfoundchar,findstring,commentflag,fieldname
--let the user know something is going on
set the cursor to watch
if (commentflag = 1 or commentflag =3) and fieldname = handler then
--search the rest of the field after the last match for the next
--match
put line lastfoundline + 1 to (the number of lines in bkgnd field¬
handler) of bkgnd field handler into temp1
put findloc(temp1,findstring) into temp2
if temp2 is not empty then
add item 1 of temp2 to lastfoundline
put getscrollline(lastfoundline,bkgnd field handler) into scroll
set the scroll of bkgnd field handler to scroll
select line lastfoundline of bkgnd field handler
exit mouseup
end if
end if
if commentflag > 1 then
put line lastfoundline + 1 to (the number of lines in bkgnd field¬
functions) of bkgnd field functions into temp1
put findloc(temp1,findstring) into temp2
if temp2 is not empty then
add item 1 of temp2 to lastfoundline
put item 2 of temp2 into lastfoundchar
put getscrollline(lastfoundline,bkgnd field functions) into scroll
set the scroll of bkgnd field functions to scroll
select line lastfoundline of bkgnd field functions
exit mouseup
end if
end if
beep
put Sorry. No other match found.
end mouseUp
Listing 4: These handlers go into the stack script
on closestack
-- when leaving check to see if you should do garbage collection
-- if there is more than 15k free then compact the stack
checkfreespace
end closestack
function popmenu list,loc
-- input: list of options,location of menu
-- output: number of menu item picked 0 if none or cancel
-- this takes a list of options,and a menu location and displays
-- a menu. It also checks to see if the user picks the cancel option
if the number of items in list < 1 then
put Cancel into list
end if
put ((the number of items in list) + 10) into nlist
put redolist (list) into list
put item 1 of loc+50 into h
put item 2 of loc+50 into v
-- This is Andrew Gilmartins Popup Menu XFCN
get PopUpMenu(list,nlist, v, h)
if it is 0 then
put You must hold the mouse button down on this button to see the
menu
end if
return it
end popmenu
function redolist list
--input list in format a,b,c,d
--output list in format a;b;c;d
-- this just reformats the list for popupmenu to use ; as the
-- item seperator not ,
put empty into newlist
repeat with x=1 to the number of items in list
put item x of list & ; after newlist
end repeat
return newlist
end redolist
on deleteField
-- this protects you against accidentilly deleting a field
answer Do you really want to delete this Field? with ok or No
Way!
if it is not ok then
click at 999,999
exit deletefield
else
checkfreespace
pass deletefield
end if
end deletefield
function linechoosen loc
--input x,y location of the mouse click(use the clickloc)
-- This is a generally useful function which takes the clickloc as
-- input and finds out which line in a scrolling field was clicked
on
put second item of loc into pos
put pos - the top of the target into pos
put pos + the scroll of the target into pos
get the textsize of the target
put it+trunc(it/3) into size
put trunc(pos/size)+1 into linehit
return linehit
end linechoosen
on checkfreespace
-- This checks on the amount of garbage needing collecting
get the freesize of this stack
if it > 15000 then
put Please wait,Im doing some garbage collection into message
domenu compact stack
hide message
end if
end checkfreespace
Listing 5: These go into the bkgnd and are used by the scripts in the
two
scrolling fields to get the identity of the script that should be
edited
function finditemname temp
--input a string
--output the address of the handler/function defined in that line
-- if the line doesnt contain such an address empty is returned
put empty into address
put offset(:,temp) into colon1
-- if the line doesnt contain a : it is not a valid line
if colon1 < 1 then
return address
exit finditemname
else
--here get rid of everything prior to the address
put findsegment(temp) into temp1
put item 2 of temp1 into temp
--here loop over the address segments
-- the first segment will be the actual object and the second will
-- be the card or bkgnd that its in.
repeat with i=1 to 2
put findsegment(temp) into temp1
put item 1 of temp1 into tempaddress
-- if the item is in the stack card or bkgnd script go right to
it
if tempaddress is stack and i=1 then
put this stack into address
return address
exit finditemname
else if word 1 of tempaddress is bkgnd and i=1 then
put insertquotes(tempaddress) into address
return address
exit finditemname
else if word 1 of tempaddress is card and i=1 then
put insertquotes(tempaddress) into address
return address
exit finditemname
else
if i=1 then
put insertquotes(tempaddress) into item 1 of address
else
put insertquotes(tempaddress) into item 2 of address
if word 1 of tempaddress is bkgnd then
put bkgnd before item 1 of address
end if
end if
end if
put item 2 of temp1 into temp
end repeat
return address
end if
end finditemname
function insertquotes tempaddress
--input string
--output string with quotes around all but first word
put the number of words in tempaddress into temp
put 1 into start
if word 1 of tempaddress is word 2 of tempaddress then
put 2 into start
end if
put word start of tempaddress into tempadd
repeat with k=start+1 to temp
put after tempadd
if start is 1 and k is (start+1) then
put quote after tempadd
end if
put word k of tempaddress after tempadd
end repeat
if start = 1 then
put quote after tempadd
end if
return tempadd
end insertquotes
function findsegment temp
--input a string
--output segment after last colon,segment before last colon
put the number of chars in temp into len
repeat with i=len down to 1
if char i of temp is : then
put char (i+1) to len of temp into segment
put char 1 to i-1 of temp into item 2 of segment
return segment
exit findsegment
end if
end repeat
return temp
end findsegment
Listing 6: This goes into the script for the two scrolling fields
on mouseup
global hit,temp,address
put the clickloc into loc
put linechoosen(loc) into hit
put line hit of bkgnd field handler into temp
--get the address of the object(card button test of card huh?)
put finditemname(temp) into address
if address is empty then
put Sorry. This line doesnt contain the name of an object.
exit mouseup
end if
--addresses are of two types those that work from anywhere
--edit the script of this stack for example or those that only
--work in a bkgnd or card such as edit the script of button test
if item 2 of address is empty then
edit the script of address
else
go to (item 2 of address)
edit the script of item 1 of address
end if
end mouseup