Advanced Programming
In this chapter, we will discuss the more advanced features of Terrapin Logo. As a “casual” user, you will probably not need to read this section. If you want to write an app that uses controls like buttons, or if you want to work with mouse or touch events, this chapter is for you.
Layouts
Note: In Classic Logo, the layout values were very different, Also, the Classic Logo variables :INITIAL.LAYOUT and :DEFAULT.LAYOUT are not supported anymore.
When you save your workspace, Logo saves the current panel layout along with your workspace. The :LAYOUT variable lets you get or set the current layout.
A layout is a list of three elements. The first is the name of the left or upper panel, and the thrid element is the name of the right or lower panel. The middle value defines whwere the splitter is. This valus either begins with “H” for a horizontal or “V” for a vertical splitter, followed be the percentage that the left or upper panel should occupy.
An example: a split view with the Graphics and Listener panel, where the Graphics panel occupies 60% of the space is [GRAPHICS H60 LISTENER]
.
Furthermore, you can use another three-element list instead of a panel name. For example, the ES layout defines a split view of the Graphics and Listener panels to the left, and the Editor panel to the right: [[GRAPHICS H60 LISTENER] V60 EDITOR]
.
You can store the value of :LAYOUT elsewhere and set LAYOUT to that value later:
MAKE “MY.LAYOUT :LAYOUT ; …do something with a different layout… MAKE “LAYOUT :MY.LAYOUT
The Icon Bar
Note: In Classic Logo, the icon bar could be hidden.
The Icon Bar’s LOCATION property lets you control where the icon bad should be located. Its value is one of LEFT, TOP, RIGHT, or BOTTOM.
Anchoring
When you create a user interface with a few controls, the controls would usually move when the Graphics panel is resized. Often, this is not desirable, because you want the controls to stay in one place.
Every widget, including bitmaps, turtles, and controls, has an ANCHOR
property. This property takes a list of two words (actually, you can also use a single word to set only a single anchor). Its purpose is to anchor the widget so it keeps a constant distance to an edge of the Graphics panel. We have these words:
LEFT
: The widget keeps a constant distance to the left edge, meaning that it does not move horizontally.RIGHT
: The widget keeps a constant distance to the right edge; it moves horizontally when the panel is resized.CENTER
: This is the default; the widget is anchored to the center of the panel.TOP
: The widget keeps a constant distance to the top edge, meaning that it does not move vertically.BOTTOM
: The widget keeps a constant distance to the bottom edge; it moves vertically when the panel is resized.MIDDLE
: This is the default; the widget is anchored to the center of the panel.
This is especially convenient for controls that want to stay at the same, position regardless of the size of the Graphics panel. If you anchor a control at [LEFT TOP]
, for example, it will stay at the same position
and not move if the panel is resized. An anchor of [RIGHT BOTTOM]
would cause the control to move with a constant distance to the right and bottom edges, so if it is in the panel’s lower right corner, it would stay there.
You would still have to position the controls initially, and figure out where to place them by looking at the size of the control, and the size of the Graphics panel.
This is how you get a control’s size:
GPROP "MYCONTROL "SIZE
And this is how you get the size of the Graphics panel:
GPROP "GRAPHICS "DRAWSIZE
So what if anchoring does not meet your needs? Well, you can also define a WHEN handler that is called whenever the size of the Graphics panel changes. Here is how do do this:
WHEN [GRAPHICS DRAWSIZE] [my list of commands]
But please keep in mind that the list of commands should be short; the event handler may be called very often if you resize the Graphics panel!
Macros
The new .MACRO and .DEFMACRO commands let you define macros. A macro is almost a Logo procedure, with one significant difference: A macro is supposed to output a list of commands that Logo runs after the macro ends.
Huh?
OK, this is complicated stuff. Let’s try a very simple example:
.MACRO MY.PRINT :WHAT
OUTPUT LIST "PRINT :WHAT
END
Here, MY.PRINT 5
would output the command [PRINT 5]
, which Logo runs
afterwards to print 5
.
But why do we need such stuff?
Let us assume that you define a new loop command that runs a runlist that you supply as input to your new loop command; something like this:
TO MYLOOP :runlist
; omitting the control code...
RUN :runlist
END
Now, use this command within some other procedure:
TO SOME.PROCEDURE :N
...
MYLOOP [IF :N = 0 STOP]
...
END
What would now happen if Logo executed the STOP command? You would probably want your procedure to exit. But in the real world, since MYLOOP
is a procedure, MYLOOP
would exit instead of SOME.PROCEDURE
, which is definitely not what you have intended!
And this is where macros have their advantage. Since a macro outputs a list, which Logo then runs within the calling procedure,
STOP would not exit MYLOOP
, but SOME.PROCEDURE
, as you have intended (please note that the simple
example above outputs the list that you fed into MYLOOP
, so the STOP command is part of that list).
Let us look at a real-world example. Here is your own version of the REPEAT command:
TO MY.REPEAT :num :instructions
IF :num = 0 [STOP]
RUN :instructions
MY.REPEAT :num - 1 :instructions
END
And here is the procedure that uses this command:
TO GUESS
PRINT [GUESS THE SECRET WORD. YOU HAVE 3 TRIES]
MY.REPEAT 3 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]]
PR "SORRY!
END
If you ran this procedure, you would always have 3 tries even if you guessed the secret word, because the STOP command would end MY.REPEAT
, not GUESS
!
It would work if you rewrote MY.REPEAT
to be a macro:
.MACRO MY.REPEAT :NUM :INSTRUCTIONS
IF :NUM <= 0 OUTPUT []
OUTPUT SENTENCE :INSTRUCTIONS(LIST "MY.REPEAT :NUM - 1 :INSTRUCTIONS)
END
This looks more complicated than it is. Thankfully, the MACROEXPAND command is here to display the list of commands that a macro outputs. Try MACROEXPAND with the MY.REPEAT
command above :
MACROEXPAND [MY.REPEAT 3 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]]]
Result: [IF READWORD = "SECRET [PRINT "CONGRATS! STOP] MY.REPEAT 2 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]]]
After applying some color to the result to improve its readability:
[ IF READWORD = “SECRET [PRINT “CONGRATS! STOP] MY.REPEAT 2 [IF
READWORD = “SECRET [PRINT “CONGRATS! STOP]] ]
You can see, the first part is the :INSTRUCTIONS
runlist for MY.REPEAT
followed by a new call of MY.REPEAT
with :NUM
set to one less along with the :INSTRUCTIONS
runlist. And, this entire runlist is evaluated within the context of the GUESS
procedure so that the STOP command would exit GUESS
as intended.
Keep in mind that any macro output is run immediately in the context of whatever called the macro. What an example of dynamic code generation!
Background Programs
Logo programs can run in the background, meaning that the Listener is free to do other things while a program is executing. You can, for example, send a turtle across the screen, while running another Logo program at the same time.
Consider this procedure:
TO CREEP
ASK 1 [
SETPC 2 ST PD
SETSPEED 0.1
SETH HEADING + (RANDOM 60) - 30
FORWARD RANDOM 20
]
CREEP
END
CREEP
lets the turtle creep across the screen forever, until you click the Stop button.
Now if you want the turtle to creep without blocking the Listener, you use the LAUNCH command to launch the procedure:
LAUNCH "CREEP
Logo starts running the procedure, but the Listener prompt returns at once, and you can enter more commands while the turtle creeps across the screen.
Creepy, huh? (No pun intended.)
But how do you stop the procedure? Well, LAUNCH returned a number, which is the ID number of the background program. Feed this number to the HALT command to halt the background program.
You can, of course, also click the Stop button, or you could use the HALT command without inputs to stop all background programs:
(HALT)
Example:
> LAUNCH “CREEP Result: 84 > HALT 84 >
Be very careful with background programs! All Logo programs share the same environment. A background program should not change any global names so other Logo programs can continue running.
Changing global variables can have grave consequences. Imagine a Logo program writing to :STANDARD.OUTPUT to create a file. At the same time, a Logo background program creates another file and changes :STANDARD.OUTPUT to write to that file, and all of a sudden, all Logo programs write to the
new file at the same time! Or just try to TELL a different set of turtles while the above CREEP
procedure runs in the background. All of a sudden, a different set of turtles will start to creep.
By the way: See Logo’s WHEN Something Changes for details.
Debugging background programs
Having multiple programs running at the same time can be difficult to debug. WHenever you enter the debugger, it displays the currently active program, which may not be your main program.
All programs (the main program as well as every background program) run in their own Logo engine; every engine has its own ID, which is a positive integer. The main engine always has the ID #0. The debugger displays the currently active engine.
While debugging, you can use the global variable :LOGOENGINE to figure out which engine you are debugging (the debugger displays the engine number as well). You can switch between engines just by setting the variable to a different value:
MAKE "LOGOENGINE 5
If there is no engine 5, Logo throws an error. Use the LAUNCHED command to see a list of active engine numbers.
Finally, the BACKTRACE command displays the stack of active procedures for any engine, including the time when the engine was launched.