textfiles/bbs/FIDONET/JENNINGS/STANDARDS/script.doc.txt

701 lines
22 KiB
Plaintext
Raw Blame History

.he Fido/FidoNet/FidoTerm Script Language Copyright Tom Jennings #
Tom Jennings
Fido Software
164 Shipley
San Francisco CA 94107
SCRIPT LANGUAGE MACHINE MODEL
Fido/FidoNet's script language is a machine language, and like all<6C>
machine languages, there is an underlying "model" machine that<61>
the script instructions manipulate. The buckets and Beetles, that<61>
is.
The script machine model has the following components at your<75>
disposal:
NAME DESCRIPTION
&A The numeric "accumulator", values 0 - 65535
&C The "clock", counts up, in seconds
&T The "trap register", 0 or 1 - 65535 seconds
&E The "error register", either 0 or 1
&R The general purpose Recognizer
&S The "shift register", 20 characters,
&1 - &8 General purpose registers
stack 10 levels of data stack
&A: The "accumulator" (such a quaint word) is a simple<6C>
register for storing or manipulating numbers. It can be set to<74>
any value from 0 to 65535. It can be stored into any other<65>
register. There are instructions to test when the accumulator is<69>
zero.
&T: The "trap register" is part of a mechanism to break<61>
endless loops; the "TRAP" instruction is used to set a failsafe<66>
if a script or part of a script takes too long to execute.
&E: The "error register" can be either 0 or 1, with 1<>
generally indicating an error condition. There are instructions<6E>
to set and test the error register.
&R: The Recognizer can be used to store text or numbers.
&1 - &8: These are general purpose registers, and can hold<6C>
numbers or text. They are used to pass parameters to subroutines.<2E>
Each subroutine level has it's own set of these registers. When a<>
subroutine is called, the initial values are those of the calling<6E>
routine.
&S: The "shift register" is a special register that contains<6E>
the last 20 characters received from the modem, and is what<61>
patterns are matched against. Characters are shifted in from the<68>
right, and the left most character "falls off" the end. You can<61>
also store text in &S and use the "IF" instruction to do string<6E>
comparisons.
&1 through &8 are "local variables"; they are not shared amongst<73>
all subroutine levels. When a CALL is executed, the called script<70>
"inherits" the calling scripts initial values for these<73>
registers, but any changes made are lost upon RETURN.
The stack is a general purpose "push down" stack; you PUSH items<6D>
onto the stack, and POP them off. Items can only be accessed from<6F>
the top of the stack. There is room for up to ten items on the<68>
stack. If there are (for instance) five items on the stack, to<74>
get at the first one pushed (the "bottom" of the stack) you must<73>
first pop off the four on "top" of it.
.pa
INSTRUCTION SYNTAX
All instructions are of the form:
INSTRUCTION OPERAND OPERAND
Blank lines or lines beginning with a semicolon are ignored.<2E>
Lines beginning with a colon (label statements) are ignored when<65>
processing instructions.
Operands are usually text strings, and all strings should be<62>
quoted.
INSTRUCTION PROCESSING
The script processor is basically a text processor. The macro<72>
language applies to all commands at all times. Anything to the<68>
right of the instruction itself is expanded by the macro<72>
processor.
Macros are expanded from left to right, once. There are two<77>
special tokens the macro processor understands:
&(one letter register name)
\(special text character)
The language chosen allows just about every disgusting trick<63>
possible with machine language; self-modifying code, computer<65>
GOTOs, etc. I'll dispense with the formality, and show some<6D>
generalized examples and leave it at that:
Message "This is a string"
The MESSAGE command merely displays what it's given on the<68>
command status line. It makes for a simple example. In this case,<2C>
centered in the status line you'd see:
This is a string
If the recognizer (&R) contained the string "HELLO DUDE" the<68>
instruction:
Message "The recognizer contains &R"
Would display:
The recognizer contains HELLO DUDE
To display the same thing, but with quotes surrounding the<68>
contents of the recognizer, you would use:
Message "The recognizer contains \"&R\""
Displayed as:
The recognizer contains "HELLO DUDE"
Note the \ before the quote character merely quotes what follows.<2E>
There are some convenient exceptions:
\r carriage return, 13 decimal
\n line feed, 10 decimal
\e escape, 27 decimal
\b backspace, 8 decimal
\c etx (Control-C), 3 decimal
\1 SOH character, decimal 1
\127 DEL character, decimal 127
... ...
If you don't like that, ("C" programmers will like it though) you<6F>
can enter control characters like so:
^A 1 decimal, ASCII SOH
^M carriage return, 13 decimal
^J linefeed, 10 decimal
... ...
The ADD instruction adds a value to the accumulator. For example:
ADD 27
Would add 27 to the accumulator; if it had contained 3, it would<6C>
now contain 30. If the &2 register contained "34", the following:
ADD &2
Would add the numeric value 34 to &A. If &2 contained "ABRACADAB"<22>
then it would add zero to &A.
Here are some further examples. Assume first:
&A contains 20
&R contains "Text"
&1 contains "764-1629"
&2 contains 300
INSTRUCTION OPERAND EXPANDED
message "sample text" sample text
message &R 20
message "pattern is &r, OK?" pattern is 20, OK?
message "&A&R&A" 20Text20
message "Dialing &1 at &2 baud" Dialing 764-1629 at 300 baud
You can take this to absurd extremes. The instruction JMP <label><3E>
passes execution to the line following the line containing<6E>
:<label>. You can take advantage of this by using a "computed<65>
goto". The command:
jmp "&R"
Would attempt to jump to a line:
:TEXT
.pa
.sh Pattern Matching
PATTERN MATCHING
The main purpose of Fido/FidoNet's script language is pattern<72>
matching; looking for particular strings of characters received<65>
from the modem. All of the script language instructions are<72>
intended to work together to do this.
The single most important component of the script machine is the<68>
Shift Register. The shift register contains the last 20<32>
characters received from the modem, and works like this:
Assume that with your modem and Fido/FidoNet, you just called into<74>
and connected with some bulletin board system, and are about to<74>
be asked to log in.
As characters are received they are put into the right hand end<6E>
of the shift register; the character on the left edge of the<68>
shift register is lost. For example, if the bulletin board signon<6F>
message you'd see on your screen was:
WELCOME TO THE ACME HOG THROWERS BBS
AT THE PROMPT ENTER YOUR NAME AND PASSWORD:
Lots of times a simple "wait until the word XYZ" will do exactly<6C>
what you want. This sort of thing is easy to write scripts for,<2C>
and even in complex scripts is a very useful function.
The problems start when you need to base different actions on a<>
number of possible patterns, not just one. A simple "MATCH"<22>
instruction just won't do it, and brute force techniques of<6F>
searching through a screenful of text ("rummaging") has serious<75>
limitations.
In Fido/FidoNet the technique used involves a 20 character array<61>
called the "shift register", referred to as "&S" in the script<70>
language.
The shift register changes after each character received from the<68>
modem; in the example above, W, E, L, C, O, M, E, etc. Part way<61>
through the above message being displayed, the shift register<65>
would look like:
....
&S " WEL"
&S " WELC"
&S " WELCO"
&S " WELCOM"
....
&S "LCOME TO THE HOG THRO"
....
Fido/FidoNet compares patterns you specify against the right hand end<6E>
of the shift register; this means that for each character<65>
received (therefore a new string each time) you can look for any<6E>
reasonable number of possible patterns. ("Reasonable" will be<62>
discussed later ...)
In this example, to do an automatic login, once we see the word<72>
"PASSWORD:", we can output thename and password. This is a common<6F>
and simple case. The instruction "MATCH" will do the job.
"MATCH" compares text you specify against the contents of the<68>
shift register. For this example we'd use "WORD:". A string match<63>
is used, so you only need to use enough characters to make it<69>
unique; the string "WORD:" does not appear anywhere else in the<68>
initial signon message. (Actually, ":" will work fine here, but<75>
it's a good idea to use at least a couple of characters; "RD:"<22>
would be even better.)
MATCH "WORD:"
To continue the example above, as characters come in from the<68>
modem they shift through the shift register. Fido/FidoNet is<69>
executing the "MATCH" instruction above, and the shift register<65>
looks like:
....
match WORD:
&S= "OUR NAME AND PASSWOR"
match WORD:
&S= "UR NAME AND PASSWORD"
match WORD:
&S= "R NAME AND PASSWORD:"
For each character received, the "MATCH" instruction is comparing<6E>
it's pattern ("WORD:") against the contents of the shift<66>
register. When the patterns match, the next line of the script<70>
file is executed, which in this case could be an instruction to<74>
output your name and password.
.pa
COMPLEX PATTERN MATCHING
You don't need to understand the how and whys of the shift<66>
register to use the "MATCH" command, and for most casual script<70>
writers that will be all you need.
A good example of the limitations of such a simple system is a<>
script that dials phone numbers with a Hayes modem using "AT"<22>
commands, and responding to the many possible "result codes".<2E>
(This example assumes a basic understanding of Hayes modem<65>
commands.)
The dialing part is easy; you just issue the ATDT command and the<68>
phone number. The problem arises when you try to interpret the<68>
results of that command, such as busy, no answer, connecting at<61>
various baud rates, errors, etc. A simple "MATCH" instruction<6F>
just won't work.
For example the US Robotics Courier HST has about a dozen<65>
possible responses to a simple dial command; any script you write<74>
must be able to handle them all. That's what we'll do here.
Dialing a US Robotics Courier HST
The command we'll use, for simplicity, is:
"AT DT 764 1629\r"
("\r" means "carriage return, CR, decimal 13)
The modem can respond with any of the following result words:
CONNECT (300 baud)
RING (incoming call)
NO CARRIER (failed to connect)
ERROR (bad command given)
CONNECT 1200
NO DIAL TONE
BUSY
NO ANSWER
CONNECT 2400
RINGING
VOICE
CONNECT 9600
This is where you take advantage of the shift register. The<68>
script fragment below handles the problem at hand and is<69>
explained below. You will be using this SAMPLE ... IF ... GOTO<54>
loop to do most complex string compares.
&T= 60
&C= 0
TRAP dial-failed
output "ATDT 764-1629\r"
:get-result
sample
if "NO" dial-failed
if "ERR" dial-failed
if "12" conn-1200
if "24" conn-2400
if "96" conn-9600
if "ECT\r" conn-300
if "BU" redial
if "VO" dial-failed
if "RING\r" dial-failed
goto getresult
:dial-failed
&A= 0
return 1
:conn-300
&A= 300
goto connect
:conn-1200
&A= 1200
goto connect
:conn-2400
&A= 2400
goto connect
:conn-9600
&A= 9600
:connect
$$ "b &A\r"
message "connected at &a baud"
return 0
"SAMPLE" is a special instruction for use in SAMPLE/IF/JMP loops,<2C>
and causes one character to shift into the shift register. (The<68>
"MATCH" instruction and most others do this automatically, but<75>
"IF" doesn't.) You should have one, and only one, "SAMPLE"<22>
instruction before each set of "IF" instructions; if you have<76>
none, the shift register will not ever change, and if you have<76>
more than one you will miss patterns.
"IF" compares the given pattern against the current contents of<6F>
the shift register, and branches to the specified label if a<>
match is made.
The patterns searched for by the "IF" instructions need to be<62>
mentioned. You could search for the entire modem response<73>
("CONNECT 1200" instead of just "12") but the script will run<75>
faster is short strings are used. Use as few as possible is the<68>
general rule. The "NO" pattern will match any of the following,<2C>
which all mean no connection: NO CARRIER, NO DIAL TONE, NO<4E>
ANSWER. The 300 baud connection result code is "CONNECT", hard to<74>
distinguish from "CONNECT 1200" because the "IF" will match<63>
"CONNECT" without waiting for the "1200" or whatever follows. The<68>
"ECT\r" means that it will only match that word at the end of a<>
line, that is, followed by a CR character.
A loop of this length will run pretty slowly. It really should<6C>
have a "timeout" check, using the &C clock, to limit the dialing<6E>
attempt to a minute or so, and this will slow it further.
To accomodate this, Fido/FidoNet has rather huge buffers, and using<6E>
the explicit "SAMPLE" instruction means that characters won't<>
speed through the shift register faster than you can test them.
With a loop of approximately this size, it would take a few<65>
thousand characters at continuous 9600 baud to cause character<65>
loss. In the example above, speed is not a problem; the modem<65>
issues very little text.
SAMPLE/IF/JMP loops should be as tight as possible. This is<69>
interpreted, so keep that in mind; keep comments out of loops,<2C>
keep labels and operands short. Use as few "IF"s as possible.
If you need to do such a series of compares through a large<67>
amount of text at high speed, it is best to cut down the search,<2C>
for example by issuing commands to reach some intermediate point<6E>
from which you can do your multiple compares.
.pa
FURTHER NOTES ON PATTERN MATCHING
The strings you provide to the "MATCH" and "IF" instructions have<76>
some characteristics that make for easier comparisons.
All searches are case IN-sensitive. "ANC" matches "anc", etc. You<6F>
can specify it in your script in either way.
"?" is a wildcard character; it mean match any character.<2E>
"CONNECT ??00" will match "CONNECT 1200", "CONNECT 2400", etc.
Control characters are treated just as any other character. "End<6E>
of line" characters will vary with the bulletin board program or<6F>
whatever it is you call; Fido for instance is like most BBSs, and<6E>
uses a CR/LF sequence (decimal 13, decimal 10). You can search<63>
for "\r" or "\13" or for "\r\n" or "\13\10", but keep in mind<6E>
that other systems could use LF/CR, or other combination.
HINT: You can store strings directly in to the Shift Register and<6E>
then test it with the "IF" command. For instance, user input from<6F>
the "INPUT" command copied into &S could be tested for a<>
particular value.
.pa
.sh Register and Arithmetic Instructions
These are the most basic instructions to change the contents of<6F>
the various registers. Please note that the space after the "="<22>
is signifigant.
&S= text
&1= text or number
&2= text or number
&3= text or number
...
&8= text or number
Set the specified register. Numbers are really text<78>
strings, but you can treat them either way. The string must be<62>
under 20 characters.
&A= value
Set the accumulator to the specified value. The<68>
accumulator is numeric only.
&B= value
Set the baud rate register to the specified value; if the<68>
value is not a valid baud rate then the &B register will not<6F>
change.
&C= value
Set the free-running clock to an initial value. The clock<63>
counts up continuously every second.
&T= value
Set the trap register to the specified value. Setting it<69>
to zero disables the trap.
ADD value
SUB value
Add or subtract the value to the accumulator.
PUSH text
Puts the text onto the top of data stack. There are only<6C>
10 levels so watch out. Anything can be pushed onto the stack.
POP register
Take the top item off the stack and place it in the<68>
specified register. If you try to put non-numeric text into a<>
numeric register it becomes "0". You cannot POP items not PUSHed.
.pa
.sh Flow Control Instructions
TRAP label
This instruction is used to set a time limit on script<70>
execution, usually loops searching for a pattern. The trap uses<65>
the &C clock.
The &T trap register is set to the specified number of seconds,<2C>
and the &C clock is reset. Before each script instruction is<69>
executed, the clock is compared against the trap time; if the<68>
time limit is exceeded, the script branches to the specified<65>
label in the script file that initially set the trap; therefore<72>
the trap can even abort scripts running many subroutine calls<6C>
deep. Setting the &T trap register to zero disables the trap.
&T= 0
&C= 0
TRAP label
....
If at any time during script execution the &C clock<63>
reaches or exceeds the &T trap register, script execution<6F>
proceeds from the specified label in the current script file.
JMP label
Branch to the specified label unconditionally.
JZ label
JNZ label
Branch to the specified label in the current script file<6C>
if the accumulator is zero (JZ) or not zero (JNZ). This is used<65>
to test previous arithmetic instructions.
DJNZ label
A basic looping instruction: "Decrement and Jump if Not<6F>
Zero". Subtracts 1 from &A, and if the result is not zero,<2C>
branches to the specified label.
JERROR label
Branches if the &E error register is not zero. &E is<69>
generally set by the MATCH instruction failing, the RETURN (from<6F>
subroutine) instruction.
CALL scriptfile parameters ...
Execute the script file as a subroutine. The filename is<69>
"scriptfile.SCR". The called script file can in turn call other<65>
scripts, and each executes until the end of the script file or a<>
RETURN instruction is executed.
Each subroutine has it's own set of registers, &1 to &8.<2E>
Parameters can be passed to the called routine, which set the<68>
initial value of the local registers. The contents of the local<61>
registers are lost when the subroutine returns.
RETURN value
Return to the calling subroutine (if any) and optionally<6C>
set the error register to the specified value.
.pa
.sh String Comparison Instructions
MATCH pattern limit
Wait until the pattern is found in the incoming character<65>
stream before executing the next instruction in the script. The<68>
pattern can contain "?" wildcard characters. There is an implicit<69>
limit of 60 seconds; if a match is not found with the time limit<69>
the error register is set and the next instruction executed.<2E>
(Presumably a JERROR ... instruction.) Optionally a time limit,<2C>
in seconds can be specified.
SAMPLE
Shifts one character into the shift register; to be used<65>
in SAMPLE/IF/GOTO loops.
IF pattern label
Compares the pattern against the contents of the shift register, and branches to the specified label if it matches. The pattern can contain "?" wildcard characters.
.pa
.sh Character Output Instructions
OUTPUT string
Queue the string for output to the modem, simulating<6E>
manual keyboard entry. The speed with which characters are output
is determined by the THROTTLE setting.
COPY string
Output the string directly to the modem at maximum speed.
THROTTLE n (0 - 500)
This controls the maximum speed that Fido/FidoNet will output<75>
characters queued up by the OUTPUT command. The speed is<69>
specified in a minimum wait between characters, in milliseconds.<2E>
The default is 20 milliseconds.
.pa
.sh File I/O Instructions
Fido/FidoNet's file system is very simple; there can only be one file<6C>
open at a time. It is not a cooincidence that the file format is<69>
compatible with most BASICs; each record in a Fido/FidoNet file<6C>
contains 8 fields, each field is a quoted string seperated by<62>
commas. Each record is delimited with a CR/LF. There is no<6E>
Control-Z or other end of file terminator.
FOPEN filename
Opens a file for reading or writing. The script is<69>
aborted if the file cannot be found, or if "UNATTENDED" is set,<2C>
then the error register &E is set. The file must be FCLOSEd when<65>
not needed.
FNEW filename
Creates a new file of the specified name. The script is<69>
aborted if the file cannot be created. The file will be empty<74>
after creation.
FREAD
Reads one record from the file, and deposits the contents<74>
of the record into the eight registers &1 to &8. Each record can<61>
contain any number of items, but only the first 8 will be used.<2E>
Each should be an argument as described elsewhere, preferably<6C>
quoted.
Lines are read from the file one per FREAD instruction,<2C>
starting with the first line in the file, until the last line is<69>
read; all FREADs after the last line will leave all registers<72>
empty and set the error register &E.
FWRITE
A new record is added to the end of the open file, that<61>
contains the contents of the eight registers &1 to &8. Each is<69>
"quoted". The script is aborted if the disk becomes full, or if<69>
"UNATTENDED" is set, then the error register &E is set.
th
FCLOSE
This closes the current file and allows the FOPENing of<6F>
another file. If any FWRITEs were done, they are written out to<74>
disk at this time.
.pa
.sh User Input Instructions
KEYBD char
Temporarily suspend script file operation until the user<65>
types the specified key; CR if none specified. All characters the<68>
user types, including the terminating one, are outout to the<68>
modem. This allows the user to manually enter something such as<61>
password.
INPUT prompt
The prompt is displayed and the user enters a line of<6F>
text, which is parsed into variables &1 through &8. Any extra<72>
text is ignored.
ASK question
The question is displayed and Y or N is input as a<>
response, with &E set true if the answer is Y.
PAUSE prompt
The prompt is displayed and the user must hit any key to<74>
continue.
MESSAGE message
Output the message to the command status line.
.pa
.sh Special Instructions
These instructions generally take one operand and perform some<6D>
hopefully useful instruction. They are generally high level<65>
functions to perform common functions.
IOPORT n (1 - 2)
Select the serial port to do business with; the default<6C>
is the first port, on a pclone COM1.
CD-BIT (1,2,4,8,16,32,64,128)
Set the Carrier Detect (CD) mask bit for proper operation<6F>
of the on-screen "ONLINE/OFFLINE" display.
TIME hh:mm (00:00 - 23:59)
Wait until the specified time of day before executing the<68>
next line in the script file.
FILES filespec
Sets &A to the number of files matching the file<6C>
specification.
DELAY milliseconds (1 - 65000)
Delay execution of the script until time elapsed.
QUIET milliseconds (1 - 65000)
Wait for no input from the modem for the specified time.
DTR
Lowers the modem control line Data Terminal Ready (DTR)<29>
for 1/2 second.
BREAK
Causes a 1/2 second line break.