Floo: A Glk-Native Scripting Language
Version 0.5
Andrew Plotkin <
[email protected]>
Floo is a simple, Glk-native scripting language. It is closely based on
the syntax of PostScript, which in turn in based on Forth and other
stack-centric languages. Like PostScript, Floo is easy to parse, runs
quite efficiently for an interpreted language, and has even less
punctuation than Lisp.
(Note, however, that Floo is not PostScript. PostScript has built-in
graphics and page layout operators. Floo has Glk operators. Floo is also
considerably simpler than PostScript in many ways; I didn't try to
implement all of PostScript's functionality. And there are other
differences here and there, for example string behavior.)
For current Floo information, interpreters, documentation, and sample
code, see the Floo home page at <
http://www.eblong.com/zarf/glk/floo/>.
1 A Floo Program
2 What's In Floo
2.1 Integer
2.2 Boolean
2.3 Null
2.4 String
2.5 Name
2.6 Operator
2.7 Mark
2.8 Array
3 A Note on Garbage Collection
4 Execution
5 Errors
6 Operators and Predefined Names
6.1 Predefined Names
6.2 Stack Operators
6.3 Arithmetic and Boolean Operators
6.4 Relational Operators
6.5 Dictionary Operators
6.6 Flow of Control Operators
6.7 Array Operators
6.8 String Operators
6.9 Conversion Operators
6.10 Miscellaneous Operators
7 Glk and Floo
1: A Floo Program
A Floo program, or script, is a text file; you edit it with a standard
text editor.
The first line must begin with the five characters "#FLOO" (upper case
required.) This indicates that the program really is a Floo program. The
rest of the first line is ignored.
Everything after that line is program text. The character "#" begins a
comment, and the rest of that line is ignored. Other than that, all
whitespace is treated equally -- tabs, spaces, and line breaks -- so you
can format your program as you like.
Floo does not care what kind of line breaks you use (Mac, Unix, or
DOS-style.)
2: What's In Floo
The basic objects in Floo are, well, objects: integer objects, string
objects, boolean objects, and so on. There are also array objects, which
contain other objects.
Objects can be stored in two places. First, there is the system
dictionary, which keeps objects associated with names, so that you can
easily look them up. Second, there is the stack -- a standard
last-in-first-out stack of objects. All operators work by manipulating
objects on the stack.
These are all the kinds of objects that exist:
2.1: Integer
A 32-bit signed integer. In a program, an integer is written as a
decimal number, or a hexadecimal number with the 0x prefix. Some
examples of integers:
0 73 -21 0xFF1C
You can use upper or lower case in hexadecimal numbers.
2.2: Boolean
True or false. You can write boolean objects as:
true false
[[Actually, these are not themselves boolean objects, but names which
are bound to boolean objects in the system dictionary. Don't worry about
this for now.]]
2.3: Null
There is exactly one null object. It is used as a placeholder to mean
"no object". It is written as:
null
(Again, this is actually a name bound to the null object in the system
dictionary. No matter how many times you write null, you always get the
same null object.)
2.4: String
An array of 8-bit characters. You write a string in double quotes:
"Hello" "One.\nTwo.\n" "Cry \"Havoc...\" and let slip the dogs."
As in C, \n means a newline, \" means a double-quote character, and \NNN
is any character value (where NNN is a three-digit octal number). You
can continue a string over more than one input line by putting a
backslash at the end of each line.
A Floo string has a maximum length and a current length. The maximum
length is fixed; once the string is created, it cannot be made longer.
However, the string can be made shorter, or changed, as long as it never
exceeds its maximum length.
(In fact, the current length of a string is determined by a null byte
within it, just as in C. You can bypass the string operators and store a
null byte directly into the string, or overwrite a null byte, thus
changing its current length. Floo always stores an extra null byte after
the maximum string position; this ensures that the current length never
exceeds the maximum length.)
2.5: Name
A name is an array of characters, like a string, but names are optimized
for fast lookup in the system dictionary. Names cannot begin with a
digit (or they would be confused with integers.) Some examples of names:
add jell_o frog123 /add /jell_o /frog123
A name can be literal or executable. The forward-slash at the beginning
determines which. /add and add are the same name, but /add is literal
and add is executable. We'll explain the difference momentarily.
2.6: Operator
An operator is an internal Floo procedure. There are many operators in
the system dictionary, and you execute them to do work.
You cannot write an operator directly in your program. You write an
executable name, and Floo finds the associated operator in the system
dictionary. For example:
dup add pop
These find (and execute) the three operators which duplicate an item,
add two items, and pop an item. The operators themselves have no written
form. However, it is convenient to write add when we mean the addition
operator, so we will do that throughout this document.
2.7: Mark
This is a special kind of object, which (merely by existing) marks a
position on the stack. It is mostly used in constructing arrays; see
below. In a program, a mark looks like one of these:
mark [
(Actually, these two names are bound to an operator which creates a mark
object on the stack.)
2.8: Array
An array is a list of Floo objects. The length of an array, like the
maximum length of a string, is fixed when the array is created. The
contents of an array do not have to be all the same type. You write an
array like this:
[1 2 3] [ "Hello" /there ] [ 1 true [] ]
Note that arrays can contain any kind of object, including other arrays.
Arrays can be zero-length.
Like names, arrays can be literal or executable. The ones shown above
are literal. Executable arrays, also called "procedures", look like
this:
{ 1 2 add } { true }
Although these two forms look similar, they are actually parsed rather
differently. More on this later.
3: A Note on Garbage Collection
In a Floo program, you are continually creating objects. Merely writing
a number creates an integer object, for example. The Floo interpreter
keeps track of these objects, and throws them away if they are no longer
accessible from either the stack or the system dictionary.
This process is automatic, and you should never have to worry about it.
However, the current version of Floo uses an extremely stupid
reference-counting system. Everyone knows that reference-counting fails
when there are circular references. So if, for example, you insert an
array into itself -- or create two arrays that contain each other --
those objects will never be deallocated.
Fortunately, this is not something you will generally want to do. If you
must, it's best to untangle the knot by overwriting the array with null
objects before you stop using it.
4: Execution
Your program is nothing but a list of objects. The Floo interpreter
reads them in and executes them, one at a time.
Here's what happens when an object is executed:
Literal objects -- including integers, booleans, strings, nulls, marks,
literal names, and literal arrays -- are simply pushed into the stack.
If your program looks like:
1 2 "Hello"
..then Floo will push the integer 1 on the stack, then the integer 2,
then the string "Hello", and then exit.
When an executable name is executed, Floo looks it up in the system
dictionary, and executes whatever it finds. If your program looks like
1 2 add
..then Floo will push 1, then push 2, and then look up the name add.
This is bound to the addition operator, so Floo executes that.
When an operator, such as the addition operator, is executed, it
performs whatever function it was meant to do. In the case of add, this
pops two numbers off the stack, adds them, and pushes the result back on
the stack. So the program shown above will end with the integer 3 on the
stack, and nothing else.
When a procedure (executable array) is executed, Floo goes through it
and executes the objects inside it, in order. Consider the following
program:
/increment
{ 1 add }
def
5 increment
The first line pushes the literal name increment on the stack. The
second line pushes the executable array { 1 add }. The third line
executes the def operator, which binds a name to an object in the system
dictionary; in this case, the name increment is bound to the executable
array { 1 add }. Then 5 is pushed. Finally, the executable name
increment is executed. This means that Floo looks up increment in the
system dictionary, finds { 1 add }, and executes that -- which means
pushing 1, and then executing add. The 1 and the 5 are popped off, and 6
pushed back on.
Got it? That's all that happens in a Floo program.
There are some details. For example, in the second line of that program,
Floo encounters an executable array -- but it's pushed on the stack,
instead of actually being executed. This is a special case. Executable
arrays are not executed when they are read in, only through other means
such as system-dictionary lookup. (Without this exception, it would be
impossible to define procedures.)
Also note that the 1 and add in the procedure are *also* not executed
when they are encountered. Objects in braces are not executed; they are
just wrapped up in the procedure which is created.
This is *not* true of literal arrays. You may recall that [ is an
operator which pushes a mark object on the stack. This is in fact how
arrays are created. In the program
[ 1 2 3 ]
..first a mark is pushed, then 1, then 2, then 3. Then the ] operator
is executed. This pops objects off the stack until a mark is reached,
and then assembles them into an array, and pushes the array back on the
stack.
There is nothing special about the [; it does not prevent execution of
objects. The program
[ 1 2 3 add ]
..produces the array [1 5], because when the ] operator is executed,
the stack contains a mark, then 1, then 5.
5: Errors
Things can screw up. Since there is no compilation in Floo, errors
always occur at run-time. When an error occurs, it is handled in a
standard way, which you can override if necessary.
Every error has a name. These are the errors currently defined in Floo:
* stackunderflow: An operator attempted to pop more operands than
were available on the stack.
* typecheck: An operand on the stack was the wrong type.
* rangecheck: A value was out of range (for example, beyond the
bounds of an array).
* undefined: A name being looked up had no definition in the system
dictionary.
* undefinedresult: The result of an operation was undefined (for
example, division by zero).
* unmatchedmark: An array being constructed failed to find a mark on
the stack (that is, there was a ] with no matching [).
When an error occurs, Floo first restores the stack to the state it was
in before the operator was called. Floo then writes the name of the
error (a literal name) into a special dictionary called errinfo, bound
to the name errorname. It also writes the offending operator bound to
the name command. The error name is then looked up in a second special
fictionary called errdict. The result will be a procedure; Floo executes
this. That's all. Unless the procedure modifies the flow of control,
Floo will continue executing the program after the offending operator.
However, all the error-handling procedures in errdict generally *do*
modify the flow of control. By default, they are all the same, and look
like this:
{
handleerror
stop
}
handleerror is an operator that displays information about the error,
using the data that was squirreled away in errinfo. stop causes the
program to stop.
You may redefine any of the error-handlers in errdict. You may also
redefine handleerror to change the way error information is reported.
The standard handleerror operator opens a new Glk window to display its
information. If you start messing with the error-handling system, be
aware of this.
[[Floo's error-handling procedure is vaguely related to that of
PostScript, but it is not the same at all. Don't make assumptions.]]
6: Operators and Predefined Names
Floo has a big pile of useful operators. Most are modelled after
PostScript operators, although the syntax is not always exactly the
same.
These sections give examples in the form
*items* operator => *result*
..showing the state of the stack before and after the operator
executes.
6.1: Predefined Names
These are not operators per se; they are names in the system dictionary
which are bound directly to certain objects.
true and false are bound to the boolean objects *true* and *false*.
null is bound to the null object.
6.2: Stack Operators
dup duplicates the top item on the stack.
*obj* dup => *obj* *obj*
exch exchanges the top two items on the stack.
*obj1* *obj2* exch => *obj2* *obj1*
pop removes the top item on the stack.
*obj* pop =>
index makes a copy of an item a given distance down the stack. The
original item, and the items above it, remain on the stack unchanged.
*obj0* 0 index => *obj0* *obj0*
*obj2* *obj1* *obj0* 2 index => *obj2* *obj1* *obj0* *obj2*
copy makes a copy of *count* items at the top of the stack. The original
items remain on the stack below the new copies.
*obj1* *obj2* *...* *objn* *count* copy => *obj1* *obj2* *...*
*objn* *obj1* *obj2* *...* *objn*
0 1 2 3 4 2 copy => 0 1 2 3 4 3 4
0 1 2 3 4 4 copy => 0 1 2 3 4 1 2 3 4
roll rotates the order of the top *count* objects on the stack. If
*shift* is positive, the objects are shifted upwards, with the top ones
moving around to the gaps below. If *shift* is positive, the objects are
shifted downwards.
*obj1* *obj2* *...* *objn* *count* *shift* copy => *obj(1-shift)%n*
*obj(2-shift)%n* *...* *obj(n-shift)%n*
0 1 2 3 4 4 2 roll => 0 3 4 1 2
0 1 2 3 4 5 3 roll => 2 3 4 0 1
0 1 2 3 4 4 -1 roll => 0 2 3 4 1
6.3: Arithmetic and Boolean Operators
add adds the top two items on the stack, and puts the sum back on the
stack.
*num* *num* add => *num*
3 7 add => 10
sub subtracts the top item on the stack from the second item.
*num* *num* sub => *num*
3 7 add => -4
mul multiplies the top two items on the stack.
*num* *num* mul => *num*
3 7 mul => 21
idiv divides the second item on the stack by the top item. This is
integer division, and the result is rounded towards zero.
*num* *num* idiv => *num*
7 3 idiv => 2
-7 3 idiv => -2
3 7 idiv => 0
mod takes the remainder of dividing the top two items on the stack. The
sign of the result is the same as the sign of the first operand.
Note that (a idiv b) * b + (a mod b) is always equal to a.
*num* *num* mod => *num*
7 3 mod => 1
-7 3 mod => -1
7 -3 mod => 1
abs takes the absolute value of the top item on the stack.
*num* abs => *num*
7 abs => 7
-7 abs => 7
neg takes the negative of the top item on the stack.
*num* neg => *num*
7 neg => -7
-7 neg => 7
bitshift performs an unsigned (logical) shift of the second item by the
top item.
*num* *num* bitshift => *num*
7 1 bitshift => 14
7 -1 bitshift => 3
not performs a logical or bitwise *not* of the top item on the stack.
*bool* not => *bool*
*num* not => *num*
true not => false
false not => true
-2 not => 1
and performs a logical or bitwise *and* of the top two items on the
stack.
*bool* *bool* and => *bool*
*num* *num* and => *num*
true true and => true
true false and => false
false true and => false
false false and => false
5 6 and => 4
or performs a logical or bitwise *or* of the top two items on the stack.
*bool* *bool* or => *bool*
*num* *num* or => *num*
true true or => true
true false or => true
false true or => true
false false or => false
5 6 or => 7
xor performs a logical or bitwise *xor* of the top two items on the
stack.
*bool* *bool* xor => *bool*
*num* *num* xor => *num*
true true xor => false
true false xor => true
false true xor => true
false false xor => false
5 6 xor => 3
6.4: Relational Operators
eq determines if the top two items on the stack are equal. It puts true
on the stack if they are, and false if they are not.
Boolean and integer objects are equal if they have the same value. Names
are equal if they look the same. All marks are equal, and all nulls are
equal. Strings are equal if they have the same *current* length and are
identical through that length. (Differences after the first null byte
are ignored.) Arrays are equal only if they are the very same object;
arrays are *not* compared element by element.
Objects of different types are never equal.
*obj* *obj* eq => *bool*
4 5 eq => false
false false eq => true
"too" "tooth" eq => false
[ 1 2 ] dup eq => true
[ 1 ] [ 1 ] eq => false
/this /this eq => true
ne determines if the top two items on the stack are not equal. It uses
the same rules as eq.
*obj* *obj* ne => *bool*
lt determines if the second item on the stack is less than the top item.
*num* *num* lt => *bool*
3 5 lt => true
3 -5 lt => false
le determines if the second item on the stack is less than or equal to
the top item.
*num* *num* le => *bool*
gt determines if the second item on the stack is greater than the top
item.
*num* *num* gt => *bool*
ge determines if the second item on the stack is greater than or equal
to the top item.
*num* *num* ge => *bool*
6.5: Dictionary Operators
def binds an item to a literal name in the system dictionary. You may
then use the executable form of the name to look up and execute the
item.
*name* *obj* def =>
/seven 7 def
seven 1 add => 8
/square { dup mul } def
3 square => 9
load looks up a literal name in the system dictionary, and puts what it
finds on the stack. (The item it finds is not executed.) If the name is
not defined, an error occurs.
*name* load => *obj*
/seven 7 def
/square { dup mul } def
seven load => 7
square load => { dup mul }
bind looks through a procedure. For each executable name it finds, it
looks up what that name is bound to; if it is an operator, the name is
replaced with the operator it represents. (Names bound to other objects
are left unchanged.)
Running bind on a procedure is useful for two reasons. First, the
procedure will run faster, since it can execute operators directly,
instead of having to look up their names in the system dictionary.
Second, the procedure cannot become confused if the names of standard
operators are later redefined.
*proc* bind => *proc*
{ 1 2 add } bind => { 1 2 *<operator:add>* }
/increment { 1 add } bind def
/add { sub } def
5 increment => 6
6.6: Flow of Control Operators
exec executes the top item, in the usual manner. That is, executable
names are looked up and executed, procedures are executed, and literal
items are pushed back on the stack unchanged.
*obj* exec => *...*
{ 1 2 add } exec => 3
27 exec => 27
1 2 /add load exec => 3
if executes one item, but only if a boolean is true. If the boolean is
false, the item is discarded without effect. if removes both items from
the stack before executing, and does not itself push anything back; but
the procedure it executes may affect the stack.
*bool* *obj* if => *...*
true { 1 3 add } if => 4
false { 1 3 add } if =>
ifelse is like if, but it executes one of two items and discards the
other.
*bool* *obj1* *obj2* ifelse => *...*
true { 1 3 add } { "hello" } ifelse => 4
false { 1 3 add } { "hello" } ifelse => "hello"
loop executes an item repeatedly, until exit or stop is executed. If the
item does not contain one of these operators, this will probably cause
an infinite loop or stack overflow.
*obj* loop => *...*
{ "hello" exit } loop => "hello"
1 loop => *...stack overflow with 1's*
repeat executes an item a fixed number of times, unless exit or stop is
executed first.
*num* *obj* repeat => *...*
5 1 repeat => 1 1 1 1 1
4 { "hello" exit } repeat => "hello"
for executes an item repeatedly. A number is pushed on the stack before
every repetition, and the number is incremented each time. The loop ends
when the number exceeds a given limit. (The increment may be negative,
in which case "exceeds" means in the negative direction.) (If the
increment is zero, the loop will never terminate.)
In general, the procedure you repeat will use the pushed number for some
purpose. If it does not, the numbers will accumulate on the stack.
Again, the exit and stop operators will exit the loop prematurely.
*initial* *increment* *final* *obj* for => *...*
1 1 5 { } for => 1 2 3 4 5
6 -2 1 { } for => 6 4 2
0 1 1 5 { add } for => 15
exit terminates a loop immediately. (If there are several nested loops
in progress, it terminates the innermost one.) Execution continues
normally after the loop's exit point. If there is no loop in progress,
the program terminates.
exit =>
1 1 5 { dup eq 3 { exit } if } for => 1 2 3
continue terminates this repetition of the innermost loop. Execution
continues with the next repetition, or after the loop's exit point if
this was to be the last repetition. If there is no loop in progress, the
program (counterintuitively) terminates.
continue =>
4 { "one" continue "two" } repeat => "one" "one" "one" "one"
stop terminates the entire program.
stop =>
6.7: Array Operators
Note that some of these operators are polymorphic, and can be used on
both arrays and strings. See section 6.8, "String Operators".
array creates an array of a given length, filled with null objects. (It
is legal to create an array of zero length.)
*num* array => *array*
3 array => [ null null null ]
mark pushes a mark object onto the stack. [ is bound to the same
operator.
mark => *mark*
[ => *mark*
] pulls items from the stack until it encounters a mark object. It
discards the mark, and creates an array containing the other items
(topmost item at the end.)
*mark* *obj0* *obj1* *...* *objn* ] => *array*
[ 1 2 "hey" ] => *array containing 1, 2, and "hey"*
length determines the length of an array (or a procedure, which is an
executable array.)
*array* length => *num*
[ 1 [ 2 3 ] "hey" ] length => 3
get extracts a single element from an array. Arrays are indexed from 0
to n-1, where n is the length of the array.
*array* *num* get => *obj*
[ 1 [ 2 3 ] "hey" ] 2 get => "hey"
put puts a single element into an array, discarding the element that was
there originally. Arrays are indexed from 0 to n-1, where n is the
length of the array.
*array* *num* *obj* put =>
/arr [ 1 [ 2 3 ] "hey" ] def
arr 2 true put
arr => [ 1 [ 2 3 ] true ]
getinterval creates a new array containing a subsequence of a given
array. The new array consists of *length* elements starting at *start*.
*start* and *start+length* must be numbers in the range 0 to n, where n
is the length of the array. (Note that it is legal to get a zero-length
subsequence beginning at n.)
*array* *start* *length* getinterval => *subarray*
[ 1 [ 2 3 ] "hey" ] 1 2 getinterval => [ [ 2 3 ] "hey" ]
[ "a" "b" "c" ] 2 1 getinterval => [ "c" ]
putinterval replaces a subsequence of one array with the elements from
another. The replaced elements are discarded. All the elements in
*array2* are written into *array1*, starting at position *start* -- this
must not run outside the bounds of *array1*.
*array1* *start* *array2* putinterval =>
/arr [ 1 [ 2 3 ] "hey" ] def
arr 1 [ "wob" 31 ] putinterval
arr => [ 1 "wob" 31 ]
aload extracts all the elements of an array onto the stack, last element
on top. Then it pushes the array back onto the stack, on top of
everything else.
*array* aload => *obj0* *obj1* *...* *objn-1* *array*
[ 1 2 3 ] aload => 1 2 3 [ 1 2 3 ]
astore takes an array, and fills it by pulling enough objects from the
stack to write into every position. Then it pushes the array back onto
the stack.
*obj0* *obj1* *...* *objn-1* *array* astore => *array*
2 "foo" /thing [ 1 2 3 ] astore => [ 2 "foo" /thing ]
1 2 3 3 array astore => [ 1 2 3 ]
6.8: String Operators
Remember that Floo strings are somewhat chimeric. A string is basically
a fixed-length array of characters, and any character can occur at any
position, including null characters. However, many operators deal with
the "current" length of the string, which is a C-style string -- the
portion up to (but not including) the first null byte. (And there is an
immutable null byte after the end of the array, to ensure that the
current length never exceeds the maximum length.) There are also
operators that write a C-style string into a string object. These can
write as many characters as source string contains, but they also write
a null byte after that, so that the destination's current length is set.
(Unless the source's current length is equal to the destination's
maximum length. In that case, the null byte would fall outside the
destination; but that position is the destination's immutable terminal
null, so it's already taken care of.)
Some of these operators are polymorphic, and can be used on both arrays
and strings. See section 6.7, "Array Operators".
string creates a string of a given length, filled with null bytes. (The
maximum length is therefore whatever you specify, and the current length
is zero.) (It is legal to create a string of maximum length zero.)
*num* string => *string*
16 string => ""
length determines the *maximum* length of a string.
*string* length => *num*
"vzefh" length => 5
strlen determines the *current* length of a string.
*string* strlen => *num*
"vzefh" strlen => 5
strcat concatenates one string onto another. The first operand is put
back onto the stack, containing the (current) values of both strings
together. It must have a maximum length large enough for this. [[If the
strings may be of any length, you may want to allocate a new string for
the concatenation, as shown below.]]
*string1* *string2* strcat => *string1*
12 string "hello" strcat => "hello"
12 string "hey" strcat "ho" strcat => "heyho"
/newstrcat {
exch
2 copy
strlen exch strlen add
string
exch strcat exch strcat
} def
"wrong" "foot" newstrcat => "wrongfoot"
get extracts a single character from a string. The result will be
between 0 and 255. The position you give must be between 0 and n, where
n is the string's maximum length. If you specify n, the result will
always be zero -- the string's immutable terminal null.
*string* *num* get => *num*
"ABC" 1 get => 66
"ABC" 3 get => 0
put puts a single character into a string. The position you give must be
between 0 and n-1, where n is the string's maximum length. (As a special
case, you may legally write a zero value at position n, which of course
has no effect.)
*string* *num* *byte* put =>
/str "ABCDE" def
str 2 69 put
str => "ABEDE"
str 2 0 put
str => "AB"
str 2 67 put
str => "ABCDE"
getinterval creates a new string containing a subsequence of a given
string. The new string has a maximum length of *length*, and consists of
*length* characters starting at *start*. *start* and *start+length* must
be numbers in the range 0 to n, where n is the length of the array.
(Note that it is legal to get a zero-length subsequence beginning at n.)
*string* *start* *length* getinterval => *substring*
"ABC" 1 2 getinterval => "BC"
"frogs" 2 1 getinterval => "o"
putinterval replaces a subsequence of one string with the characters
from another. All the characters in *array2* are written into *array1*
-- the maximum length, not just the current length, but excluding the
terminal null. They are written starting at position *start*; this must
not run outside the bounds of *array1*.
*string1* *start* *string2* putinterval =>
/str "ABCDE" def
str 1 "xyz" putinterval
str => "AxyzE"
6.9: Conversion Operators
cvs writes a text representation of an object into a given string. If
the text representation is longer than the maximum length of the string,
an error occurs. The string is then pushed back on the stack.
Numbers are represented as signed decimal values; booleans are "true" or
"false". A string appears as its current length, and a name appears as
it was defined. All other objects appear as the string
"--nostringval--".
*obj* *string* cvs => *string*
45 "ABCDE" cvs => "45"
/add 16 string cvs => "add"
cvx converts a literal name or array to an executable one. Other types
of objects are not affected.
*obj* cvx => *obj*
[ add 1 2 ] cvx => { add 1 2 }
/add cvx => add
cvlit converts an executable name or array to a literal one. Other types
of objects are not affected.
*obj* cvlit => *obj*
{ add 1 2 } cvlit => [ add 1 2 ]
/add cvx cvlit => /add
cvn converts a string to a literal name. The name will contain the same
text as the (current value of the) string. Note that you can use this
operator to create name objects like " " or "1X2", which cannot be
entered directly in a Floo program.
*string* cvn => *name*
"add" cvn => /add
6.10: Miscellaneous Operators
echo takes the top object from the stack and writes it to the current
Glk output stream. This output is somewhat smarter than cvs. It writes
arrays and procedures as lists of objects in brackets or braces,
distinguishes executable names from literal names by putting slashes
before the latter, and generally formats its output to be readable by
humans.
*obj* echo =>
echostack writes everything on the stack to the current Glk output
stream (top object rightmost.) The stack is not changed.
*...* echostack => *...*
handleerror displays a batch of useful information about the most recent
error. It draws this from the special dictionary errinfo, which is
filled in by Floo when an error occurs.
handleerror first opens a new Glk text-buffer window (three lines high
at the bottom of the screen, or filling the screen if there are no
windows in existence yet). It then prints out the error name, the object
whose execution caused the error, and the (current) contents of the
stack.
handleerror =>
glk invokes a Glk function, using the dynamic linking mechanism of Glk.
The Floo language is not hardwired with a set of Glk functions. Instead,
the library exports its capabilities dynamically, and Floo makes them
available to the user.
*num* determines which function to call. The number and nature of the
operands depend on the function. See section 7, "Glk and Floo".
*...* *num* glk => *...*
7: Glk and Floo
Glk is the I/O layer through which Floo communicates with the universe.
Glk handles *all* Floo input and output. A Floo program should usually
start with Glk calls to open a text window; if it does not, the user
will not be able to see anything happen.
All Glk activity ultimately takes place through the glk operator.
However, for convenience, the Floo interpreter queries the Glk library
about its capabilities, and creates a set of definitions which the
program can use.
For example, the glk_put_string() call is assigned function number
0x0082 (decimal 130). You can invoke this by writing:
"Hello.\n" 0x0082 glk
However, you can also write:
"Hello.\n" glk_put_string
glk_put_string is a procedure, not an operator; it is defined just as if
you had written:
/glk_put_string {
0x0082 glk
} bind def
All Glk functions available in the library will have such procedures
defined. In addition, numeric constants (which are given as macro
definitions in the C header file glk.h) will be available in the system
dictionary as integer objects. So, for example, you can open a
text-buffer window and make it the current output stream with the
following code:
0 0 0 wintype_TextBuffer 0 glk_window_open
/mainwin exch def
mainwin glk_set_window
The operands of Glk functions are easy to understand for functions that
take only integers and strings. Opaque Glk objects, such as windows and
streams, are represented in Floo as integer identifiers; so these are
easy to handle as well.
Matters become more confused when we consider functions that take arrays
and pointers. Briefly, all Glk pointer arguments are classified as
pass-in, pass-out, or in-out parameters. In addition, NULL may or may
not be a legitimate value for any pointer. When you call a Glk function
in Floo, the glk operator looks up the function's argument list, checks
the stack to make sure the operands are valid, calls the function, and
then puts the appropriate operands back on the stack.
* Pass-in non-NULL arguments must be given as a value on the stack.
* Pass-in NULL-ok arguments may be given as either a value or a null
object.
* Pass-out non-NULL arguments should not be given at all; the
function will create a value and leave it on the stack.
* Pass-out NULL-ok arguments must be given as a *boolean* value.
This indicates whether you want the function to pass a pointer and leave
the resulting value on the stack, or pass NULL and return nothing.
* In-out non-NULL arguments must be given as a value on the stack;
an updated value will be left on the stack after the function call.
* In-out NULL-ok arguments may be given as either a value or a null
object. If a value is given, an updated value will be left on the stack;
if null, nothing will be returned.
At the present time, string arguments to Glk are always pass-in non-NULL
parameters. When you pass a string object, the current value of the
string is passed to the function.
Arrays, however, are more complicated. A single Floo array represents
*two* Glk function arguments, an array-of-integer and an array length.
Similarly, a Floo string represents two Glk function arguments, an
array-of-char and an array length. Array arguments *also* follow the
pass-in-out-null rules above -- except that pass-out arguments should be
given as arrays or null objects, like pass-in arguments.
We understand that this is horribly confusing.
Therefore, we will list all the Glk calls in Glk API 0.5, and show how
they are called in Floo. [[Remember, as Glk is enhanced, more calls will
be available *even in this version of Floo*, as long as the Floo
interpreter is linked with the new Glk library. But this should get you
started.]]
glk_exit =>
glk_tick =>
*num* *num* glk_gestalt => *num*
*winid* *bool* glk_window_iterate => (*num*) *winid*
*winid* glk_window_get_rock => *num*
glk_window_get_root => *winid*
*winid* *num* *num* *num* *num* glk_window_open => *winid*
*winid* *bool* glk_window_close => ([ *num* *num* ])
*winid* *bool* *bool* glk_window_get_size => (*num*) (*num*)
*winid* *num* *num* *winid* glk_window_set_arrangement =>
*winid* *bool* *bool* *bool* glk_window_get_arrangement => (*num*)
(*num*) (*winid*)
*winid* glk_window_get_type => *num*
*winid* glk_window_get_parent => *winid*
*winid* glk_window_get_sibling => *winid*
*winid* glk_window_clear =>
*winid* *num* *num* glk_window_move_cursor =>
*winid* glk_window_get_stream => *strid*
*winid* *strid* glk_window_set_echo_stream =>
*winid* glk_window_get_echo_stream => *strid*
*winid* glk_set_window =>
*strid* *bool* glk_stream_iterate => (*num*) *strid*
*strid* glk_stream_get_rock =>
*frefid* *num* *num* glk_stream_open_file => *strid*
*string* *num* *num* glk_stream_open_memory => *strid*
*strid* *bool* glk_stream_close => ([ *num* *num* ])
*strid* *num* *num* glk_stream_set_position =>
*strid* glk_stream_get_position => *num*
*strid* glk_stream_set_current =>
glk_stream_get_current => *strid*
*num* *num* glk_fileref_create_temp => *frefid*
*num* *string* *num* glk_fileref_create_by_name => *frefid*
*num* *num* *num* glk_fileref_create_by_prompt => *frefid*
*frefid* glk_fileref_destroy =>
*frefid* *bool* glk_fileref_iterate => (*num*) *frefid*
*frefid* glk_fileref_get_rock =>
*frefid* glk_fileref_delete_file =>
*frefid* glk_fileref_does_file_exist => *num*
*char* glk_put_char =>
*strid* *char* glk_put_char_stream =>
*string* glk_put_string =>
*strid* *string* glk_put_string_stream =>
*string* glk_put_buffer =>
*strid* *string* glk_put_buffer_stream =>
*num* glk_set_style =>
*strid* *num* glk_set_style_stream =>
*num* glk_get_char_stream => *num*
*strid* *string* glk_get_line_stream => *num*
*strid* *string* glk_get_buffer_stream => *num*
*char* glk_char_to_lower => *char*
*char* glk_char_to_upper => *char*
*num* *num* *num* *num* glk_stylehint_set =>
*num* *num* *num* glk_stylehint_clear =>
*winid* *num* *num* glk_style_distinguish => *num*
*winid* *num* *num* *bool* glk_style_measure => (*num*) *num*
glk_select => [ *num* *winid* *num* *num* ]
glk_select_poll => [ *num* *winid* *num* *num* ]
*winid* *string* *num* glk_request_line_event =>
*winid* glk_cancel_line_event *bool* => ([ *num* *winid* *num* *num* ])
*winid* glk_request_char_event =>
*winid* glk_cancel_char_event =>
*winid* glk_request_mouse_event =>
*winid* glk_cancel_mouse_event =>
*num* glk_request_timer_events =>
Note that there are two Glk functions which are not listed.
glk_gestalt_ext() is not yet available, because I haven't figured out
what to do with its annoying overloaded third argument. And
glk_set_interrupt_handler() is not available because callbacks are ugly.
The Glk dynamic dispatch mechanism does not try to handle them. If an
interpreter wants to support them (which Floo does not), it will have to
pass its own handler to glk_set_interrupt_handler() and have that take
care of things.