Go to the first, previous, next, last section, table of contents.


Appendices

The Command Interpreter

The basis around which the command interpreter is written is a grammar which is passed a set of tokens ( analogous to words in English ) which it parses, given a set of grammatical rules. As it recognises each rule, it executes the code associated with that rule. See section The SM Grammar.

An example would be:

        aa : BB CC
           {
              printf("Rule BB CC found\n");
           }

which specifies that the rule aa consists of the token BB followed by CC, and that if rule aa is recognised the programme should print that fact out. Conventionally, uppercase names are reserved for `terminal symbols', and lowercase for `non-terminal symbols' where terminal symbols are those that are passed to the parser ( analogous to words ), and non-terminal symbols are tokens that the parser has constructed out of terminal symbols (analogous to phrases). The right hand side of a rule may contain a mixture and non-terminal symbols, and symbols may be assigned a value(22).

Token Generation

SM generates tokens for the grammar roughly as follows: When characters are typed at the keyboard, they are read by a routine which runs in CBREAK mode (PASSALL for VMS), and receives each character as it is typed. It is this routine that handles command line editing, the history system, and key bindings.(23) Following a carriage return, it passes the whole line to the lexical analyser, which divides the input stream into integers, floats, strings, or words. In addition it recognises ${}^ as having special meanings (see below under variables ($) and history (^)). As in C, the escape sequence `\n' is replaced by a newline, which means that commands which read to the end of the line may be fooled into thinking that they have found it; see the examples at the end of the section. A { sets the flag `noexpand', which turns off the interpretation of all special symbols, and causes all tokens to be returned as WORD. The matching } unsets this flag. This mechanism is used in defining macros and various lists. A word is anything which is not otherwise recognised, so for example `hello_there.c' or `1.2e' would be considered words. Symbols are separated by white space, taken to be spaces tabs or newlines, or the characters !, {, }, +, -, *, /, =, ?, !, ,, <, >, (, or ). This behaviour can be modified by enclosing a string in double quotes, when no characters (except ^) are special, and tokens are delimited only by the end of the line, or some character after the closing quote. Enclosing in quotes is rather similar to enclosing in {}, except that quotes have no grammatical significance. A string in double quotes is always treated as a word, but the quotes must not have been discarded by the time that the lexical analysis occurs. For example, "2.80" is a float, as SM will have digested the " before looking at the string. You can fool it with "2.80 ". A string begins with a ' and continues to the next ': they are used in certain contexts where SM needs to know if a WORD or STRING is involved, for example in a PRINT command. It's worth noting that the '...' are stripped when the string is recognised -- if you need to preserve them make sure that noexpand is set (e.g. SET s={ 'a' 'b' 'c'} ).

The output from this programme is passed to a second stage of lexical analysis. This passes integers and floats through unaltered, while words are passed through a filter to see if they are external tokens(24) from the grammar (such as CONNECT). If a word is recognised as being a token then that token is returned, otherwise the token WORD is passed, and the text of the word is stored. Tokens may be written in either lower or upper case, but for clarity they are written in upper case in this document. The overloading of lowercase tokens is achieved at this stage by simply refusing to recognise them as keywords.

The input stream is now fully analysed into tokens and is passed to the parser, which is written in YACC. If the sequence of tokens seen corresponds to a grammar rule, the parser executes the appropriate section of code, which is written in C. If the parser doesn't understand, it tells you that you have a syntax error and prints the last logical line that it was processing, with the error underlined. If you can't figure out which command it really failed on, try setting the VERBOSE flag to be 4 or more. This produces a voluminous output, which will stop suddenly when the error re-occurs. One simple rule in the grammar is that a WORD should be treated as a possible macro.

Peculiarities of the Grammar

If the command interpreter is faced with a pair of grammar rules such as

        AA BB CC
and
        AA BB

it may not know whether to treat the tokens AA BB as the first part of AA BB CC or as the complete command AA BB followed by the token CC beginning the next command without examining the next token. This ambiguity only arises if a command can begin CC, and may be dealt with by defining the second rule as

        AA BB \n

This should be borne in mind whenever SM complains about a syntax error in an apparently valid command (such as LIST MACRO HELP, intended as first LIST MACRO and then the valid command HELP). The presence of a required carriage return also sometimes requires that macros be spread over a number of lines rather than as one long list of commands, although a carriage return may always be written as `\n', which makes SM think that it has found a carriage return. There is a also requirement that an ELSEless IF statement should end with a newline; this is produced by a subtlety of the way that IF's are processed and is discussed under IF.

SM places a restriction upon commands such as RELOCATE which expect more than one argument, which is that the arguments must be numbers rather than (scalar) expressions. This is required by the unary minus, as if the grammar sees expr1 - expr2 it cannot know whether this is the two expressions expr1 and -expr2, or the single expression expr1-expr2. Unless the grammar is changed, for instance by using commas to separate arguments, this restriction cannot be lifted; it can, however, frequently be circumvented using macros such as rel discussed under `Useful Macros'. As an alternative, in almost all cases the expression can be enclosed in parentheses, for example connect (lg(x)) (-lg(rho)).

The Macro Processor

Executing a macro consists of substituting the text of the macro for its name. In order to understand how SM does this you have to know a bit more about how it processes input characters. We said above that it `passed the whole line' to the lexical analyser. What it actually does is to pass a pointer to the line, and starts reading from the beginning of the line. Now if you execute a macro, all that is done is that we now pass a pointer to the text of the macro, and start reading from it instead. The old pointer is pushed onto the top of a stack. When SM comes to the `\0' at the end of the macro text, the stack is popped and input continues as if the macro had never been seen. When we come to the end of the `whole line' pushed at the top of this paragraph, it is popped, and SM gives you a prompt for more input. Of course, if a macro had been seen while the first macro was being executed, the first one would get pushed onto the stack, and attention transferred to the the new one. If a macro has any arguments, their definitions are pushed onto an argument stack which is popped at the proper times. To jump ahead a little, variables are implemented in a very similar way, being pushed onto the stack, as are DO and FOREACH loops, and perhaps more surprisingly IF statements.

The strange behaviour of RETURN at the end of macros comes about because when the input routine is reading the RETURN it has to read one character beyond it, so as to know that it isn't dealing with, say, RETURN_OF_THE_NATIVE. But in looking for the next character it has to pop the macro off the stack, so when the RETURN is acted upon we have already returned from where we wanted to return from, and we now RETURN from the wrong place. In a similar way, an IF at the end of a macro will cause the parser to look for an ELSE, thereby popping the macro stack if there isn't one. If the IF test was true, and contained references to macro arguments, there will be a problem as either there will be no macros defined, or the arguments to the previous macro on the stack will be supplied.

Macro definitions are currently stored in the form of a weight-balanced tree (actually a tree). This means that the access time for a given macro only grows as the logarithm of the total number defined. In the future it may be possible to choose the weights depending on the access probability for a given macro, but this is not currently possible. Definitions of variables and vectors are stored in the same way.

The DO, FOREACH, and IF commands

It seems worth discussing the implementation of these commands. Both loops consist of a definition of a variable, together with instructions about what to do with it, followed by a list of commands within a set of {}, while IF just has the command list. It is not possible for the main grammar to execute commands or macros, as the YACC implementation is non-reentrant, so the best that it can do is to push the commands onto the input stack as a sort of temporary macro, after defining the initial value of the loop parameter. When the `\0' at the end of the loop appears, instead of popping the macro stack we simply define the loop parameter to have its next value, and jump back to the beginning. This means that you can't change the value of a loop parameter, as it'll be reset anyway, but you can use it as a sort of local variable.

IF statements are similar, in that we read the entire list before executing it. Once more, a temporary buffer is pushed onto the stack, with instructions to delete it after use. The reason that a newline is required after an ELSEless IF is that the grammar will have already read the next token to see if it was ELSE. If it wasn't, then it will seem to have been typed before the body of the IF. For example, IF( test ) { echo Hello } PROMPT : will be parsed as IF( test ) { PROMPT echo Hello } : if test is true, but correctly as IF( test ) { echo Hello } PROMPT : if it is false. Because an extra \n does no harm, we demand it.

Examples of How SM Parses Input

If you want to watch SM thinking about these examples, the command VERBOSE 4 will make it print out in detail each token as it reads it, and each macro or variable as it expands it. To turn this off, use VERBOSE 0 or VERBOSE 1. To really see the parser at work, try a negative value of verbosity. This will report every step that the parser takes, providing that it was compiled with DEBUG defined. A second negative value will turn the information off again.

PROMPT @
PROMPT is an external token, so PROMPT is passed to the grammar which recognises the rule PROMPT WORD, and sets the prompt to be `@'. When it has finished, control is passed back to the input routine.
MACRO p { PROMPT }
This is a simple macro defining p to be PROMPT
p @
The lex analyser doesn't recognise p as a keyword, so it returns WORD and as the grammar has no other interpretation of a WORD in this context, it passes p to the macro interpreter, which replaces it by PROMPT (i.e. pushes PROMPT onto the input stack). SM now thinks that you have just typed PROMPT @, and behaves as described in the first example.
MACRO pp 1 { PROMPT $1 }
The macro pp is declared to have one argument, which is referred to as $1. After pp is invoked it reads the next (whitespace delimited) word from the input stream, and replaces $1 by that word.
pp @
Just like the first example, the prompt is set to @.
pp
You are prompted for the missing argument to PROMPT.
PRMPT
As PRMPT isn't an external token, it is a WORD, so SM tries to execute it as a macro and complains if it isn't defined.
DEFINE Hi Hello
The variable Hi is defined to have the value Hello.
WRITE STANDARD $Hi Gentle User
When it has read $Hi SM pushes the value of the variable Hi onto the stack and then reads it, popping it off again when it has finished. The WRITE STANDARD command writes Hello Gentle Reader (i.e. up to the end of the line) to the terminal.
WRITE STANDARD $Hi Gentle User \n pp "SM>"
As above, the rest of the line is written to the terminal (up to the carriage return `\n'), then the prompt is changed yet again.

The Stdgraph Graphics Kernel

SM can use a single set of subroutine calls to plot on almost any terminal, and on many printers. The routines that it uses, called stdgraph, were originally taken from the IRAF GIO package written at Kitt Peak by Doug Tody(25) and converted to C and partially re-written to be integrated into SM. Despite our extensive rewrite, these routines should probably still be considered to be in the public domain.

The Graphcap File

Stdgraph uses a file called a graphcap file to specify the properties of terminals, in a way that is similar to the termcap facility of Unix. You don't have to know anything about termcap to read this section; you don't have to read this section unless you want to change the graphcap file to add a new device, to fix a bug, or to change the way that SM treats your plotting device. The name of the graphcap file is given by the variable graphcap in the environment file.

A list of files to be searched in order may be given instead of a single graphcap file (up to a current maximum of three). The usual way to accomplish this is to add an entry

+graphcap          /u/rhl/sm/graphcap

above any other graphcap entries in your `.sm' file, which instructs SM to put `/u/rhl/sm/graphcap' first in the list of files, followed by any others that might appear, either in your file or in some other that the system provides (ask the person who installed SM where the default `.sm' file is; usually something like `/usr/local/lib/.sm').

A graphcap file is a way of describing a terminal in a concise way, so a programme can discover which idiosyncrasies a terminal has without having to be recompiled. A graphcap file consists of a number of entries, one for each device supported, and to add a new terminal all that one has to do is to add another entry. It is also possible to define variables in graphcap files, which are used in SY entries. You can compile selected entries in the graphcap file, so as to improve access time for popular terminals. If this has been done, changing the graphcap file for one of these terminals will have no effect until it is recompiled, see section Compiling Graphcap for details.

For a list of all the capabilities that SM uses see the index to graphcap at the end of this appendix.

Some devices are not supported through stdgraph (graphics drawn to a SunView window would be an example), but they still appear in graphcap with a special entry (DV) giving the name of the appropriate hard-coded device driver.

Each entry consists of a name for the device, followed by a list of aliases, followed by a list of fields, separated by colons. A \ may be used to continue an entry onto the next line, and lines starting with a # are comments (comment lines are permitted both between and within entries). As a rather complex example, the graphcap entry for a Tektronix 4012 reads:

tek4010|tek4012|TEK4010|TEK4012|Tektronix 4010/2:\
        :ch#.0294:cw#.0125:co#80:li#35:xr#1024:yr#800:\
        :MC=^M:CL=^[^L:CN#6:GD=^X:GE=^[1^]:\
        :ML=^[(1$0)`($1)a($2)c($3)d($4)b($$:lt=01234:\
        :OW=^]^_:RC=^[^Z:SC=(,!3, & *, &+!1, & *, &+!2:\
        :TB=^]%t^_:VS=^]:\
        :xr#1024:XY=%t:yr#780:

This is one of the longest entries in the graphcap file - all of the terminals which are Tektronix emulators explicitly include this entry, so they only need provide the capabilities that are different from the Tektronix. As an example, the entry for a Pericom reads

pericom|Pericom:\
        :GE=^]:TB=^](2#7-!2)%t^_:\
        :tc=tek4012:

The | separate the aliases, and the final field tc=tek4012 tells stdgraph to take all other fields from the entry for tek4012, given above. If you have specified a list of graphcap files, each will be searched in order for each :tc= continuation. If you don't want the search to begin again use TC, e.g.

graphon|Graphon which claims to support lw:\
        :LW=:TC=graphon:

if you had used :tc=graphon: this would have been recursive and illegal, but as TC doesn't restart the search it merely has the effect of adding (or in general, replacing) an capability in a preexisting graphcap entry.

Control characters are entered as ^A, ^B, and so on (those are two characters, `^' and `A'). `Escape' may be represented as ^[, \E, or in octal as \033. Because the normal way of handling strings in C treats \0 as meaning `end of string' you can't simply put a \000 into a graphcap entry, instead write \377 and SM'll interpret it as \0. (If you need a real \377 enter \377\377). If a delay of so many milliseconds is required before the transmission of a string, it is given first (followed by a * if it is to be applied to each line affected). This leads to problems with graphcap entries that start with numbers, you must precede them with a space or (if the string is run through the encoder) insert a no-op e.g. :CP=()1000:. Numerical values are preceded by a #, so :co#80: means that co (the number of columns displayed) is 80, while :MC=^M: means that MC (the cursor delimiter) consists of the character ^M. This could just as well have been written :MC=\010:. If the first character of a capability is `@', it specifies that that capability is not present for that terminal (e.g. :lt@=1234: specifies that lt is not defined). A field may simply not be provided if it is irrelevant, although in this case it may be supplied by a tc or TC continuation. A common set of graphcap entries to `comment out' are TB and TE, which deal with hardware character sets. If you don't want your plotter to use it's internal fonts simply insert `@' before the `='. By inserting their private file before the system one in the list of graphcap files, users can tailor the entries to their liking.

We use a subset of the graphcap capabilities defined by the IRAF group, and the distinction between upper and lower case parameters comes from them. In a few cases our usage is different from theirs, in these cases we have specified our own capabilities (CD MC, DD SY, LT ML, and TS TB. We have also added the lt, BP, BR, CO, CS, CT, DC, DT, DV, EP, ER, and TC. capabilities.). First the lower case, which specify mostly dimensions:

ch
Height of a character, relative to the screen height being 1.0.
co
Number of columns displayable, with characters of width cw.
cw
Width of a character, relative to the screen width being 1.0.
li
Number of lines displayable, with characters of height ch.
lt
Which linetypes are supported in hardware.
pc
Pad character for delays (use NUL if not supplied).
xr
x dimension of plotting device.
yr
y dimension of plotting device.

Of these, co and li are not currently used.

The capitalised capabilities mostly tell the stdgraph routines how to plot lines, clear the screen and so forth. Some of these are no more than character strings to send to the terminal, (e.g. CL to clear a screen), but some use the graphcap entries to programme a sort of RPN calculator, which computes the bit-patterns that the terminals demand. This calculator is usually referred to as the `encoder'. We'll first list all the capabilities in a reasonably ordered way, then describe the encoder and what it can do, and then go through a number of examples.

First the fields which are simple character strings to be written to the terminal. The second column is an attempt to explain the etymology of the two character name.

CL (CLear)
Clear the screen, possibly also the text screen.
CW (Close Workstation)
Close terminal, expect no more graphics.
DS (Draw Start)
Prepare the terminal to draw a line.
DE (Draw End)
Finish a line.
FD (Fill Draw)
Draw a side of a filled polygon.
FE (Fill End)
Finish drawing a filled region.
FS (Fill Start)
Start drawing a filled region.
GD (Graphics Disable)
Return the terminal to a character mode.
GE (Graphics Enable)
Set the terminal to graphics mode.
IF (Initialisation File)
Used to supplement OW if sequence is too long.
LR (Load Registers)
(Used by the RPN encoder, see `binary encoding').
ME (Mark End)
Finish a series of dots movements.
MS (Mark Start)
Start a sequence of dots movements.
OW (Open Workstation)
Prepare a terminal to produce plots.
OX (Open workstation)
(a continuation of OW).
OY (Open workstation)
(a continuation of OX).
OZ (Open workstation)
(a continuation of OY).
PG (PaGe)
Start a new page.
VE (? End)
Finish a series of pen (beam) movements.
VS (? Start)
Start a sequence of pen movements.

For hardcopy devices PG should start a new page. The GD and GE are used by terminals which spend some of their time being graphics terminals, and some being regular text terminals. The various "... Start" and "... End" capabilities assume that the points in question are specified by the XY entry (except for FS/FE where FD is used instead). Typically, the `start' is used to put the device into (e.g.) line-drawing mode, then the line is drawn with a sequence of XY's, then it is taken out of (e.g.) line mode with the `end'. The support for filling areas assumes that a region is specified by drawing a line around it; if this isn't so, you'll have to omit area fill from graphcap, and rely on SM emulating it for you. An example would be a Graphon GO-250, which has an area fill where you fill rectangular areas by specifying opposing corners; this is not acceptable to SM.

Some operations require an argument, for instance setting the hardware line type, specifying which cursor to read(26), or specifying coordinates. In the following properties, the expected parameters are listed after the field names, the first to go into register 1, the second into register 2, and so on. If you haven't skipped forward to the section on the encoder this will seem obscure, but all will become clearer.

CO(r,g,b) (COlour)
Set next colour to (r,g,b).
CS(n) (Colour Start)
Start defining n colours.
CT(i) (Colour Type)
Set a colour.
DC (Default Colour)
Set default colour.
LW(f) (Line Weight)
Set the lineweight to f.
MC(i,x,y) (sM Cursor)
Decode a cursor response.
ML(i) (sM Line)
Set the linetype to i.
RC(c) (Read Cursor)
Read the cursor. `c' is for compatibility.
SC (Scan Cursor)
Decode the cursor reply following a RC.
TB(x,y) (Text Begin)
Start writing text at (x,y).
TE (Text End)
Stop interpreting characters as text.
XY(x,y) (X Y)
Encode the coordinate pair (x,y).

Some of the above comments are a little cryptic, but we return to the various graphcap parameters that take arguments as examples after describing the encoder. Note that it isn't sufficient to change the ML entry -- for a linetype to be supported in hardware it must also be included in the lt list, e.g. lt=01234. Similarly, for hardware fonts you must include ch and cw, and TB must be present even if it does nothing. Note that LW is passed a floating point number, and that the special case 0 is special, meaning choose the most efficient line thickness for the device.

The following capabilities have to do with rasterising and are discussed in their own section near the bottom of this appendix:

BP (Bit Pattern)
Bit patterns for rastered data.
BR(i) (Begin Row)
Begin row of rastered data.
EP (Empty Pattern)
Bit pattern for rastered empty pixel.
ER (End Row)
End of a row in rastered data.
ll (lINE lENGTH)
Length of a row for DR=hex.
MR (Many Rows)
Number of rows output at once.
nb (nUM bYTES)
Number of bytes to process at once for MR.
RA (RAster)
(RA is no longer supported -- see DV).
RD (Raster Device)
Type of device if not generic.

Raster devices also make use of xr, yr, CW, OW, OX, OY, OZ, OF, and SY which are also used by stdgraph itself.

Finally there are some capabilities that are designed for driving hardcopy devices and devices that may not use stdgraph at all:

DT (Device Type)
Type of device in use.
DV (DriVer)
Name of hard-coded driver.
OF (Out File)
The file to direct output to.
RT (Record Terminator)
(RT is no longer supported).
SY (SYstem)
The action to be taken upon closing the OF file.

The OF file may be specified with the last characters being `XXXXXX', in this case the Xs are replaced by a random characters, to make a unique filename. If the variable temp_dir is defined in the environment file, then OF is created in that directory, otherwise it is put in the current directory. The DT string, if present, specifies the type of device in use. Currently the values are only used under VMS, where they are used to decide how to open files. The recognised values are "qms" and "imagen". In general DT should be omitted, as it requires programming support, but it can help stdgraph to deal with hostile operating systems. For a discussion of the DV entry see section New Devices and New Machines.

The SY string is passed to the operating system after graphcap variables have been expanded (they are similar to macros in Unix's make). A variable is defined with a line like:

	name = value

where name must start in the first column. Any white space surrounding the equals sign is removed, as are any trailing blanks. If value starts with a $ it is taken to be a regular SM variable. Variables may be defined in any of the graphcap files in the search path, and if a name appears more than once the first value found will be used (if you change graphcap without leaving SM the variables are re-read). There is no guarantee that all the graphcap files in the path will be read but this is unlikely to be a problem. The major use for graphcap variables is probably for encoding rasterise's full name:

BIN = /usr/local/bin
device|some device:\
        :DV=raster:OF=tst_XXXXXX:\
           ...
        :SY=${BIN}/rasterise -r $0 $F $1:

Variables are written as ${name} not $name, which means that they will not (usually) conflict with the operating system's uses for dollar signs. The graphcap variable F is special, as it always expands to the filename specified as OF. As a concession to history it may be written as $F instead of ${F}. Also special are $"prompt", which is replaced by a string read from the keyboard (you are prompted with prompt), and $n which is replaced by the n'th argument to the DEVICE command. For example, if the DEVICE command were DEVICE qms lca0 Hello (or DEVICE 1 qms lca0 Hello), then the device name qms would be $0, lca0 would be $1 and Hello $2. If a `$' is found under other circumstances it is simply treated as a dollar sign, but if you wish you can escape it with a \ (but remember that the \ must itself be escaped so to explicitly escape a dollar in an SY string you must type \\$). This means that (under Unix) you can access environment variables from SY strings, e.g. :SY=mv ${F} $HOME:. If a variable is referenced but no value is provided when the device is opened a warning message is printed; this message can be suppressed by referring to the variable as (e.g.) $%1. The SY string is only used if an OF file has been specified. There is no guarantee that SY is supported by all operating systems, but it is certainly available under Unix and VMS (SY requires the C call `system()', as defined for Unix. We have provided one for VMS, and any serious SM implementation would have to have one too.) A trivial example of SY in use on an Unix system would be:

:SY=cat $F ; rm $F:OF=out_XXXXXX:

(cat prints a file, ; separates multiple commands on a line, rm deletes a file). Because not all operating systems can support multiple commands on one line, you can use \n within a SY string to separate commands. For example, under VMS that SY string could have been written

:SY=type $F. \n delete $F..*:OF=out_XXXXXX:

(Type adds a `.lis' unless explicitly given a closing `.', delete requires a version number, hence the $F. and $F..*.) An example of the use of $"" would be

:SY=mv $F $"Output filename? ":

which renames the OF file to whatever you want.

The RT capability has been deleted in version 2.0, in favour of using DT; The RA capability has been replaced in version 2.1 by :DV=raster:.

Stdgraph's Binary Encoder

Different terminals have very different ways of doing the same thing. For example to move the beam to (200,200), a vt240 in REGIS mode needs to be told `[200,259]', while a Tektronix 4010 needs `&h&H'. In order to cope with this much diversity, stdgraph has a binary encoder with a 50 element stack, 10 registers and about a dozen operators. The encoder communicates with the rest of the world through its registers - for example in encoding a coordinate pair it expects to find x in register 1, and y in register 2. When reading a graphcap string, initially stdgraph simply copies the input characters to an output string, which is then written to the terminal. This is exactly what it does when it interprets the OW string for a Tektronix, OW=^]^_. However, in addition to characters such as ^ being special, it also recognises the following as being special:

'
escape special meaning of next character
%
Begin a format string
(
Switch from copy into encode mode.

When in `encode' mode, the following operators are available:

'
Escape next character (recognised everywhere)
%
Formatted output, e.g. %d, %g, or %t
)
Revert to copy mode
#nnn
Push signed decimal number nnn onto the stack
$
Part of a switch statement
.
Pop a number from the stack, and put it in the output string
,
Get next number from input string, and push it onto the stack
`str`
Prompt with str, then read a character and push it onto the stack
&
Modulus operator (similar to an AND of the low order bits)
+
Add (similar to OR)
-
Subtract (similar to AND)
*
Multiply (a left shift if number is a power of 2)
/
Divide (a right shift if number is a power of 2)
<
Less than (0:false, 1:true)
>
Greater than (0:false, 1:true)
=
Equals (0:false, 1:true)
;
Branch, <boolean><offset>; (; is at 0 offset)
0-9
Push register 0-9 onto the stack
!N
Pop the top of the stack into register N.
!!
Pop the stack, and delay that many milliseconds.
|
Convert the top of the stack from float to int (it is rounded rather than truncated).

Unless otherwise specified the stack is taken to be integer-valued, although in fact it can support either integer or floating point values. There is no type checking -- if you ask the encoder to print the top of the stack as a float, but you stored an int, you can expect trouble. If it is needed we might add more floating point support; apart from printing the top of the stack, the only floating point operation supported is `|' which rounds the top of the stack (taken to be a float), converting it to an integer (so, for example, 1|1! converts the contents of register 1 from float to int).

All the binary operators operate on the top 2 elements of the stack, and push the answer onto the top. Any other character is interpreted as an integer, and pushed onto the stack - for instance, `' is the same as `#64', octal 100. A blank is the octal constant 040.

The % command means, `format the top of the stack, and write it to the output string'. The format string may be any printf format specifier (printf is the C formatted i/o function. In practice, the only formats that you are likely to need are %c, %d, %g and %t -- and %t isn't even in C! %c means `write the integer as a character', %d means `format the number as a decimal integer', %6d means `and make it fill 6 characters', and %g means format a floating point number. If you should need to know more, look at any book on C.) The special format %t means `take x and y from registers 1 and 2, and format them for a Tektronix'. As we shall see below, you can programme the encoder to do this, but Tektronix emulators are so common that %t is provided for efficiency's sake. In fact there are two Tektronics formats, %t for 10 bit addresses, and %T for 12 bit addresses. The switch and branch instructions are discussed below, while examining specimen ML and SC strings.

Examples of Graphcap Entries

As a simple example, the ANSI command to set a non-graphics cursor to a given line and column is

^[[ line ; column H

Assuming that the x and y coordinates are in registers 1 and 2 respectively, the corresponding graphcap string would be

"^[[(2)%d;(1)%dH"

(where the quotes are not part of the format.) What if line and column coordinates start at 1, but the terminal wants them starting at 0? then the format would be

"^[[(2#1-)%d;(1#1-)%dH"

You could write those #1's as ^A which would be slightly faster, but why bother?

As promised above, it is also possible to encode Tektronix-type coordinates. The desired bit format for a 10-bit address is

0 1 ya y9 y8 y7 y6
1 1 y5 y4 y3 y2 y1
0 1 xa x9 x8 x7 x6 
1 0 x5 x4 x3 x2 x1

where x1 is the least significant bit in x, and ya is the tenth bit in y. If x and y are in registers 1 and 2, the simplest XY (move/draw to (x,y)) string is

"%t"

but if this weren't available the following string would work:

"(2 / +.2 &`+.1 / +.1 &@+."

(as before, the double quotes don't belong to the format). To understand this, First look up the octal values of ` ' (040), "' (0140), and `@' (0100). Then the first `(' puts the encoder into encode mode. `2 /' pushes the Y value onto the stack, and right shifts it by 5 bits (` ' is 100000 in binary). The next ` +.' adds the resulting bit pattern `0 0 ya y9 y8 y7 y6' to 0100000 and transfers it to the output string, and we have produced the desired first byte. The other bytes are produced in a similar fashion.

As another example consider an AED512, which is reputed to desire the bit sequence

xa x9 x8 yb ya y9 y8
x7 x6 x5 x4 x3 x2 x1
y7 y6 y5 y4 y3 y2 y1

The graphcap string

"(#128!919/^N*29/+.19&.29&."

will accomplish this. We could further optimise this by loading the value `#128' into register 9 once and for all with the LR capability, so a part of the graphcap entry would appear as

":LR=#128!9:XY=(19/^N*29/+.19&.29&.:"

I've never seen an AED512, but this should work anyway.

The switch instruction has the form

$i ... $j-k ... $l ... $D ... $$

where i, j, k, and l are integers. The encoder pops the top value off the stack adds `0' to make it a character, and scans forward looking for a $ followed by that character. $2-5 would match the characters `2', `3', `4', or `5'. When it has met its match, it executes the instructions that it meets until it reaches the next $ in execute mode. The encoder then skips forward until just after the $$, and resumes scanning. If the character from the stack is not matched by any of the cases, the encoder will use the $D (i.e. default) case, if present.

As an example, consider how stdgraph sets the type of line to draw. SM expects linetype 0 to be solid, 1 to be dotted, and so on. We expect a linetype in register 1 and have to do something with it.

For a Tektronix, the linetypes are set by an ML entry:

ML=^[(1$0)`($1)a($2)c($3)d($4)b($$

What does this do? The ^[ is simple, it is executed in copy mode, and writes the character ^[ to the output string. The (1 enters encode mode, and places the contents of register 1, the desired linetype, on the stack. Then begins the switch. If the linetype is 0, then the encoder scans past the $0 and starts reading the string again with )`. The ) takes the encoder back to copy mode, so it copies ` to the output string, and encounters a ($ which puts it back into encode mode. Once in encode mode it recognises the $ as the end-of-case, and scans forward until it reaches $$, where it stops. We deduce that the set-linetype-0 escape sequence is ^[`. If register 1 had contained a 2, after entering the switch the encoder would have scanned forward to $2 (ignoring all characters as it went), and copied c to the output string.

If you want to support erasing of individual lines (LTYPE ERASE or LTYPE 10) you'll have to include a $\: case in your switch (as : follows 9 in the ascii character set, and an un-escaped : would end the graphcap entry). You'll have to escape the : in the lt list as well. When leaving erase mode, by specifying any other line type, the device will first be set to LTYPE 11 (i.e. ML'll get a ;) before it's set to the desired LTYPE; this gives the driver a chance to reset itself. It's wise to also turn off erase mode when closing the device. An example of an entry supporting erasing lines is a graphon, which includes

:lt=01234\:;:CW=^[1^]^[^A^[2\
:ML=^_^[(1$0)`($1)a($2)c($3)d($4)b($\:)`^[^P($;)^[^A($$:

as ^[^P puts a graphon into erase mode, and ^[^A takes it out. Note that in erase mode the linetype is set to solid (^[`), so as to erase all types of lines.

There is also a branch instruction, which has syntax

<boolean><offset>;

If the boolean is true (non-zero), then skip (offset - 1) characters in the programme string. The offset may be either positive or negative, and the `;' is at offset 0. For example,

(0#15;)Goodbye(#1#8;)Hello()\n

will print `Goodbye\n' if register 0 contains zero, or `Hello\n' otherwise. As an example of the use of `;', consider using the encoder to decode a string. Remember that `,' meant `read a character onto the stack', and that there was a graphcap capability SC to decode cursor responses. Suppose that we are dealing with a vt240 in REGIS mode, then a cursor read will return a string of the form `k[nnn,mmm]' where `k' is the character you hit, and (nnn,mmm) is the cursor position. We want to put k into register 3, and (x,y) into registers 1 and 2. This is a little messy, as we'll have to convert the ascii positions into integers. The desired graphcap entry is

SC=(#0!1#0!2,!3,#0!8,#48-!99$0-91#10*9+!1#1!8$$8#1=#-39;\
0!8,#48-!99$0-92#10*9+!2#1!8$$8#1=#-39;62-!2):

The first part is simple enough, store 0 in registers 1 and 2, store the first character in register 3, read a character (the [), and store 0 in register 8. Then we come to ,#48-!99$0-91#10*9+!1#1!8$$8#1=#-39;. The ,#48- reads a character and converts it to an digit (48 is the decimal code for `0'), then stores it in register 9. The switch then checks if we do have a digit, if so we multiply register 1 by 10 and add the new digit. We then set register 8 to 1 and finish the switch which is here being used as an if statement. The 8#1=#-39; tests register 8 against 1 (i.e. checks if we found a digit), and if we did it jumps back 39 characters, to read the next character(27). So we are accumulating the integer nnn in register 1, just as we needed to. The rest of the string deals with decoding the y coordinate.

Sometimes you don't want to read from the input string, but from the keyboard instead. In this case use `str`, e.g. (`Hello\: `#48-$0)False($D)True($$)\n: will prompt you with Hello: , then read a character from the keyboard. If you enter a `0' it'll print False, otherwise it'll print True. Of course, in reality you'd want to do something more useful (such as erasing the screen).

Using Cursors with Graphcap

We have just been through a long explanation of how to decode a cursor string, but how did stdgraph know what to read in the first place? After receiving the RC string, the terminal will send back a sequence of bytes, and the format of these bytes must be specified in graphcap.(28) There are two ways to do this, either by specifying a sequence of characters which `end' the response string along with a minimum number of characters to read, or by specifying a pattern that the terminal response is to match. A typical example of the former is a Tektronix whose cursor response may be chosen to be ^M (this is called the GIN response, and can usually be set in the terminal setup). We know that the terminal will also send 5 other bytes (the key struck and the encoded x,y coordinates so we would specify

:MC=^M:CN=6:

On the other hand, a REGIS terminal sends `k[nnn,mmm]'. This can be specified as

:MC=?[#*,#*]:CN=-6:

where the negative value of CN means that we are providing a pattern not just a terminator (as before, the absolute value of CN is the minimum number of bytes in a cursor response). In MC strings, but nowhere else, the characters ?, #, and * are special (although their special meanings may be escaped with a \). ? will match any character, # any digit, and * means `match zero or more of the preceding characters'. So a MC string of a#*?ba will match `aaa1111bbaa' at the third character. (Incidently, a#*?a would match at the first). Because this special character syntax is different from that used in standard graphcap files for IRAF, the name of this graphcap parameter has been changed from CD to MC.

If your cursor is attached to a mouse, if possible the buttons should be set up to generate `e', `p', and `q' from left to right (if you have that many buttons). If you have only one button, `p' is probably the best choice.

Using Colours with Graphcap

The number passed to CT are the same as those specified with the CTYPE INTEGER command, so initially they specify default, white, black, blue, red, green, magenta, yellow, and cyan (white is 1). These are the colours corresponding to turning one, zero, two, or three of the primary colours on. The default colour to use for a device is specified by the DC capability, e.g. :DC="red":.

The CS and CO capabilities are used to support the CTYPE = expr command. First CS is used to tell the device how many colours to expect, then CO is used for each number, with red, green, and blue as its arguments. In this case CT passes an index into the set of CO values. If you want to get an index, but don't need CS and CO, you must still provide them; just provide a no-op such as :CS=():.

How to Modify a Graphcap Entry

You might think that all that you have to do to modify a graphcap entry is to start up your favourite editor, and start typing. You could do that, many people have, but it isn't recommended because you'll have to do it again when a new release of SM comes along. It's better to use SM's graphcap search path (see section The Graphcap File) as follows:

Let us assume that you want to modify the xterm entry to print something every-time that is changes from graphics to alpha mode or vice versa (why? so as to fix a problem with excessive screen switching). First set up the system `.sm' file to look like:

+graphcap               /my/private/graphcap
graphcap                /usr/local/lib/sm/graphcap

which tells SM to first search `/my/private/graphcap' then `/usr/local/lib/sm/graphcap' (the graphcap file that we provide) for graphcap entries. Then edit `/my/private/graphcap' and add the entry

xterm|an xterm with noisy mode flipping:\
        :GE=\nE\n^[[?38h:GD=^[^C\nD\n:TC=xterm:

Note the use of :TC=xterm: which says that SM should skip this file when looking for the definition of xterm, thus avoiding an infinite loop.

When a new version of SM is released your new definition will continue to work (unless we change the definition of xterm in which case you'll have some work to do anyway). You can use this technique to change entries or add your own new ones without modifying the system file; all of your changes are in `/my/private/graphcap'.

Writing a New Graphcap Entry

So, if you're faced with a new piece of hardware what should you do? First of all, don't panic -- writing entries is quite simple. Second, see if your device is basically the same as one that already exists in graphcap, for example the entry for `graphon' uses the `selanar' entry, and it in turn uses `tek4010'. You might be able to get away with using tc to satisfy most of your device's needs.

Before writing your new entry please read the previous section to learn the recommended strategy for modifying graphcap files.

Let's assume that you are faced with a totally new type of device and really do have to start from scratch. First find out how large your device is, and fill in the xr and yr entries. If you are going to use hardware character sets you also need ch and cw. Next decide on the string to initialise the device -- does it need to be set into some weird mode -- and put it into OW. Put the string to reset it into CW. Now, if the initialised device needs to be put into a special graphics mode put it into GE and its inverse into GD. Next, you need to tell SM how to draw a line and move the plot pointer. So enter the DS, XY, DE, VS, and VE capabilities. Of course, if one isn't required, don't put it in. If you have some sort of printer you probably want to store all the commands in a file (OF=), and to plot them (SY=). You should now be ready to make your first test, so plot a box. If it doesn't look right, fix it. Or you might like to try printing the cover (load cover cover).

When all is well, you can begin looking into options that might make your graphcap entry more efficient. Look through this appendix to see what is available. Does your device support line types? Add ML and lt. Heavy lines? LW. Coloured lines or a cursor? See section Using Colours with Graphcap. Filled polygons? FS, FD, and FE. Dots? MS ME. Hardware characters? TS TE. If your device produces hardcopy you should arrange to start a new page with PG (the PAGE command). When you have finished please send us your new entry.

Support for Raster Devices

Stdgraph can only handle devices that can plot vectors specified by their endpoints; unfortunately some devices (such as most line printers) can only plot graphs when they have been reduced to rows of `on' and `off' pixels. SM supports such devices through DEVICE raster and a separate programme called rasterise. You specify that a device in a graphcap file is a raster device by using DV: :DV=raster: (The old form :RA: is no longer supported). It communicates with the rasteriser through graphcap, so the whole process is user transparent. A separate rasterising programme was written so as to allow the plot to be produced in the background while you do more productive things, and to allow the rasterising to be done on a remote machine.

DEVICE raster produces a file, whose name is specified as usual by the OF field in graphcap, containing the vectors to be plotted (as groups of four short integers) in device coordinates, where the size of the device is taken from xr and yr. When the device is closed, the command specified by SY are executed, and these will usually be of the form rasterise -r $0 $F outfile\n print_it outfile\n delete outfile where print_it is the proper way of actually getting a plot. Under Unix, the command might well be something like (rasterise -r $0 $F - | lpr -v -r -P$1)& dispensing with the temporary outfile.

What do these rasterise commands do? The command syntax is rasterise [-flags] device infile outfile, where the infile may be specified as `-' to use standard input (sys$input to VMS), where the outfile may be specified as `-' to use standard output (sys$output to VMS). Possible flags are r to remove the infile after use, R to rotate the plot through 90 degrees, and v for more verbose operation. Rasterise then reads the data in the infile, and produces a rasterised version, row by row, on the outfile. In order to do this, it looks in graphcap for an entry for device, and uses the xr, yr, OW (and O[XYZ]), and CW fields as usual.(29)

Let's first consider a simple, one-line-at-a-time device such as a line printer. Before writing each row to outfile, rasterise encodes the BR (Begin Row) capability, using the current row number as an argument, and encodes ER (End Row) at the end of the line. By default, it assumes that the raster device simply wants bits turned on where a dot is required, but this can be overridden using the BP and EP capabilities. EP (Empty Pixel) specifies the bit pattern for a character to represent white space. In the simple case mentioned a moment ago, this would be simply NUL, with no bits on, but sometimes this doesn't suffice (see examples below). BP (Bit Pattern) is a string, giving the bit patterns required to turn on the various pixels. In the default case, BP could be specified as BP=\001\002\004\010\020\040\100\200, so \001 would turn on the first (rightmost) dot. Because there are eight characters given in the string, raster assumes that it can fit eight pixels into a single character. If you don't specify a BP this is what will be used. Some devices desire or require that the data be sent as hexadecimal numbers rather than as binary; see the RD=hex graphcap entry.

Some other devices (e.g. Epson printers) choose to print several lines at a time, so a single byte transmitted to the device might print 8 lines, but only the first pixel of each line. Such devices are described to graphcap by being given the MR (Many Rows) capability and a number nb which describes how many bytes deep the printing band is (if omitted nb defaults to 1). In this case, BP is used to describe which bits are turned on vertically rather than horizontally but everything is otherwise the same as for the simple case.

As an example, consider the HP laserjet. You'd specify it as DEVICE laserjet, and its Unix graphcap entry reads:

laserjet|HP laserjet (high resolution):\
        :DV=raster:xr#1280:yr#640:CW=^[*rB:OW=^[*r1280^[*rA:BR=^[*b160W:\
        :OF=hp_XXXXXX:\
        :SY=/usr/local/sm/rasterise -r $0 $F - > /dev/hp&:

On opening the device, it gets the string ^[*r1280^[*rA, setting the resolution and raster mode. Then, at the beginning of each rastered line it gets ^[*b160W specifying that 160 bytes are coming its way, then finally ^[*rB to restore it to alpha mode. (It doesn't need to know which row it is on, so the BR string doesn't tell it, and the default BP and EP are fine). After the input file is read it is deleted, and the output file is sent to the standard output, whence it is redirected to the proper device, in this case directly rather than through a spooler.

A more complex example is a printronix printer, which encodes 6 pixels in each byte, and requires that bit 7 be turned on. It also needs an escape sequence at the end of each line. The corresponding graphcap entry is

printronix|DEC printronix printer:\
        :DV=raster:xr#792:yr#792:CW=^L:OW=^L:BR=ER=^E^J:\
        :BP=\001\002\004\010\020\040:EP=\100:\
        :OF=pr_XXXXXX:\
        :SY=(/usr/local/sm/rasterise -r $0 $F - | rsh wombat lpr)&:

We use EP to turn on the seventh bit everywhere, as required, and specify only 6 values for BP, so only 6 dots will be packed into each character. The BR entry is empty, and ER provides the needed escape sequences at ends of lines. In this case SY sends the plot over a network to machine wombat.

Some devices are not able to simply accept a string of bytes with an occasional escape sequence. For example, a versatec needs to have the bit order changed, or a simple screen plotter might want to write a * if a bit is set and a space otherwise. If this is the extent of your pathology, you can deal with it via the provided capabilities. (Fortunately adding a * onto a space makes a *, so you can use :EP= :BP=*: for the latter.) If you have a really bad device, it is possible to add new coded device drivers to rasterise. For the convenience of such devices there is a graphcap capability RD which specifies the name of a type of raster device. If rasterise recognises the device it it calls a different set of routines to deal with the rows of data. Otherwise it proceeds as discussed in the previous paragraph. This behaviour is similar to that of the DEVICE command in using stdgraph if it doesn't recognise a device name.

If you find that you do need to write routines for some device, don't be too disheartened. Rasterise will still do the book-keeping and rasterising for you, your work will be limited to a couple of output routines. If you need to know more, see the source for rasterise. The only time that I used this capability came about two years after rasterise was written, and was RD=hex which specifies that lines be written as hexadecimal numbers rather than as 8-bit characters (e.g. write the two characters FF instead of the single character `\377'). The line length is given as ll.

Compiling Graphcap

(This section is really for someone maintaining SM.) Rather than have stdgraph read the graphcap every time that it opens a devices, it is possible to compile the capabilities of the more popular devices into the executable. This is done by preparing an include file which initialises the appropriate arrays, using the programme `compile_g' in the main directory. After this file (called cacheg.dat) has been prepared, files depending on it must be recompiled and SM must be relinked. The use of compile_g is pretty much self-explanatory, you give it a list of the devices you want and it produces the cacheg.dat file. Problems arise, however, if you don't have a valid cacheg.dat file, as then you can't compile compile_g in the first case. Fortunately, it is possible to bootstrap a cacheg.dat file (by defining BOOTSTRAP to the C-preprocessor), and proceed from there.

When stdgraph attempts to use the compiled capabilities, it checks that the current graphcap file has exactly the same name as the one that cacheg.dat was compiled from, if it isn't then it reads the graphcap file anyway. This provides a mechanism for those without C compilers to change the graphcap entries of pre-compiled devices. If you have a list of graphcap files, the name of the first is checked against the name in the `cacheg.dat' file.

Index to Graphcap Capabilities

b

  • BP (Bit Pattern)
  • BR (Begin Row)
  • c

  • ch (Character Height)
  • CL (CLear)
  • CO (COlour)
  • co (number of COlumns)
  • CS (Colour Start)
  • CT (Colour Type)
  • cw (Character Width)
  • CW (Close Workstation)
  • d

  • DC (Default Colour)
  • DE (Draw End)
  • DS (Draw Start)
  • DT (Device Type)
  • DV (DriVer)
  • e

  • EP (Empty Pattern)
  • ER (End Row)
  • f

  • FD (Fill Draw)
  • FE (Fill End)
  • FS (Fill Start)
  • g

  • GD (Graphics Disable)
  • GE (Graphics Enable)
  • i

  • IF (Initialisation File)
  • l

  • li (number of LInes)
  • ll (lINE lENGTH)
  • lt (Line Type)
  • LW (Line Weight)
  • m

  • MC (sM Cursor)
  • ME (Mark End)
  • ML (sM Line)
  • MR (Many Rows)
  • MS (Mark Start)
  • n

  • nb (nUM bYTES)
  • o

  • OF (Out File)
  • OW (Open Workstation)
  • OX (Open workstation)
  • OY (Open workstation)
  • OZ (Open workstation)
  • p

  • pc (Pad Character)
  • PG (PaGe)
  • r

  • RA (RAster)
  • RC (Read Cursor)
  • RD (Raster Device)
  • RT (Record Terminator)
  • s

  • SC (Scan Cursor)
  • SY (SYstem)
  • t

  • TB (Text Begin)
  • TE (Text End)
  • v

  • VE (? End)
  • VS (? Start)
  • x

  • xr (X-Resolution)
  • XY (X Y)
  • y

  • yr (Y-Resolution)
  • Calling SM from Programmes

    Introduction to Callable SM

    The SM callable interface is different from that of Mongo and corresponds directly to the interactive version. Almost all of the commands available in SM can be called from either fortran or C, the exceptions being those concerned with the macro processor, variables, history, and vector manipulations. We assume that if you want to call graphics routines directly, then you are prepared to take responsibility for such things. In C (and probably pascal, modula, or ADA), the calls have the same names as the commands with the prefix sm_, so to set the limits say sm_limits(0.0,1.0,0.0,1.0);. On VMS, and with some unix systems (notably HPUX and AIX), if you are writing fortran, you must prepend an `f' to the command - call fsm_limits(0.0,1.0,0.0,1.0). If you forget this `f', your programmes will compile, but they `won't' work. (They'll give segmentation violations, most likely. Why? Because you will be calling the C functions directly, not the Fortran interface functions, and the languages have different ways of passing arguments, so e.g. you will pass the address of the variable from your Fortran program to a C function that expects to get the value of the variable. This translation is precisely what the interface functions are set up to do for you).(30) If your fortran compiler cannot find the function names, you might have to read the section on how SM chooses function names (see section How to Choose the Name for Fortran Subroutines).

    So how are you supposed to know what to call the functions? Well, our best suggestion is to ask the person who build SM on your system; the second best suggestion is to look in `libplotsub.a' (or `libplotsub.olb' on VMS systems), and see what the names of the functions are. On VMS, try this:

    lib/list/names libplotsub/lib
    

    and look at the names it lists under module INTERFACE. On a unix system you can use nm (or, if desperate strings). The output is quite long, so I'd filter the output, e.g.:

    nm libplotsub.a | grep errorbar
    

    If you see things in the output like fsm_errorbar, then you know this is one of those machines where you have to prepend the `f' for the fortran interface functions.

    If you used an ANSI compiler to link SM (or your guru did) then you should provide prototypes for the SM functions that you call if you use the C interface (Fortran knows nothing of such things). This can be done by including the file `sm_declare.h'.

    In what follows, we will assume that you are not writing fortran under VMS, so we will omit the leading effs.

    To use SM functions, you must link with appropriate libraries. You will always need to link with the three SM libraries, libplotsub, libdevices, and libutils in that order. Specifically, under Unix, you'll need to include the files `libplotsub.a', `libdevices.a', and `libutils.a' when you link (it's probably easier to use -lplotsub -ldevices -lutils, along with a -Ldir if needed). and under VMS you'll need `libplotsub.olb/lib', `libdevices.olb/lib', and `libutils.olb/lib'. I can't tell you where they'll be on your system. In addition, you may need to link (after utils) any libraries used by the devices that have been compiled into your version of SM. For example, if you use the X-windows driver, you'll need to link with the X-library (-lX). Consult a local guru in case of any trouble - the person who installed SM has had to work this out already.

    Example of Calling SM Functions

    A list of functions giving the calling sequence for all the available functions follows, but first an example. Note especially the use of graphics and alpha to set the terminal to graphics mode, and return to a normal terminal afterwards. We would recommend always calling these, if they do nothing (e.g. on a laser printer) they'll do no harm. A related function is gflush() which will update graphics on the screen, for instance with stdgraph where output is usually buffered.

    Some devices (such as GL on an SGI, or X-windows on machines without backing store such as RS/6000's) need some help from you to redraw the screen. You do this by calling redraw whenever you are waiting for input, passing it `0' (the file descriptor of standard input, for those of you familiar with C). When input is ready, redraw will return and your application can proceed. It should do no harm on systems where it does no good.

    If you don't want to be bothered with calling sm_alpha, sm_graphics, sm_gflush, and sm_redraw, See section Why do I have to call sm_alpha, sm_graphics, sm_gflush, and sm_redraw?.

    There are examples of programmes (in both C and fortran) calling SM in the directory sm/callable; you might want to look at them before starting your own project.

            integer NXY
            parameter (NXY=20)
            integer sm_device
            integer i
            real x(NXY),y(NXY)
    c
            do 1 i=1,NXY
               x(i) = i
               y(i) = i*i
    1  continue
    c
            if(sm_device('hirez') .lt. 0) then
                print *,'Can"t open hirez'
                stop
            endif
            call sm_graphics
            call sm_defvar("TeX_strings","1")
            call sm_limits(-1.,22.,0.,500.)
            call sm_ctype('red')
            call sm_box(1,2,0,0)
            call sm_ptype(40.,1)
            call sm_points(x,y,NXY)
            call sm_xlabel('X axis')
            call sm_ylabel('Not x axis')
            call sm_alpha
            print *,'hit any key to exit'
            call sm_redraw(0)
            read(5,*) i
            end
    

    Remember, if you were running VMS, HPUX, or AIX then all those calls would start `f' - call fsm_graphics and so forth. Note that sm_box takes all of its four possible arguments, and that commands such as sm_points add an argument to specify the number of points to plot. You must, of course, ensure that you use the correct type of variables, passing integers or reals as required (and as listed below). If you are using C, you must carefully distinguish between passing by value, and passing by address (which we only use when an array is expected, and for returning a cursor position).

    Function Definitions: 1-D

    The functions are as follows. For fuller definitions of the arguments look at the main description of the command. The only ones that are different are sm_conn for sm_connect (due to a collision with a system routine), and sm_curs, sm_defvar, and sm_plotsym because they don't quite correspond to any interactive commands. A common source of trouble is not noticing that the sm_ptype call is the vector form of the command, so to set a ptype of `4 1' you must say sm_ptype(41.0,1).

    The arguments are declared in fortran in this list. real x(n) means that x is an array of size n. `Real' means single precision. (So in C, character char *; integer int; real float; real() float *. We deal with converting the calling conventions from one language to another, but note comments at the bottom of this table.)

    sm_alpha
    Set terminal back to a normal state
    sm_angle(a)
    Set angle to a (real a)
    sm_axis(a1,a2,as,ab,ax,ay,al,il,ic)
    Draw an axis. (real a1,a2,as,ab; integer ax,ay,al,il,ic)
    sm_box(x1,y1,x2,y2)
    Draw a box - note 4 args. (integer x1,y2,x2,y2)
    sm_conn(x,y,n)
    Connect a line. (real x(n),y(n); integer n)
    sm_ctype(c)
    Set colour to c (character c)
    sm_curs(x,y,k)
    Return position of cursor (real x,y; integer k)
    sm_defvar(str1,str2)
    Select variable str1 to str2 (character str1, str2)
    sm_device(str)
    Select device str (character str); return integer
    sm_dot
    Draw a dot
    sm_draw(x,y)
    Draw to (x,y) in user coords (real x,y)
    sm_erase
    Erase screen
    sm_errorbar(x,y,e,k,n)
    Draw error bars (real x(n),y(n),e(n); integer k,n)
    sm_expand(e)
    Set expand to e (real e)
    sm_format(xf,yf)
    Set format strings to xf and yf (character xf,yf)
    sm_gflush
    Flush graphics output
    sm_graphics
    Set terminal into plotting mode
    sm_grelocate(x,y)
    Relocate in screen coordinates (integer x,y)
    sm_grid(i)
    Draw a grid (integer i)
    sm_hardcopy
    Close current device, if appropriate make hardcopy
    sm_histogram(x,y,n)
    Draw a histogram (real x(n),y(n); integer n)
    sm_identification(str)
    Identification string (character str)
    sm_label(str)
    Draw a string (character str)
    sm_limits(x1,x2,y1,y2)
    Set limits (real x1,x2,y1,y2)
    sm_location(x1,x2,y1,y2)
    Set location (integer x1,x2,y1,y2)
    sm_ltype(lt)
    Set ltype (integer lt)
    sm_lweight(lw)
    Set lweight (real lw)
    sm_notation(xl,xh,yl,yh)
    Set axis format defaults (real xl,xh,yl,yh)
    sm_plotsym(x,y,n,sym,ns)
    Draw user points (real x(n),y(n); integer n,sym(3*ns),ns)
    sm_points(x,y,n)
    Draw points (real x(n),y(n); integer n)
    sm_ptype(pp,n)
    Set point type - vector form (real pp(n); integer n)
    sm_putlabel(i,str)
    Position a label (integer i; character str)
    sm_redraw(i)
    Wait for input on file descriptor i (integer i).
    sm_relocate(x,y)
    Relocate in user coords (real x,y)
    sm_shade(delta,x,y,n,type)
    Shade a region (integer delta; real x(n),y(n); integer n,type)
    sm_ticksize(xs,xb,ys,yb)
    Set ticksize (real xs,xb,ys,yb)
    sm_window(nx,ny,x,y,x2,y2)
    Select a window (integer nx,ny,x,y)
    sm_toscreen(ux,uy,sx,sy)
    Convert to screen coordinates (real ux,uy, integer sx,sy)
    sm_xlabel(str)
    Draw x-axis label (character str)
    sm_ylabel(str)
    Draw y-axis label (character str)

    sm_device returns 0 if it opens the requested device, or -1 if it fails. You must declare sm_device as returning integer, of course. sm_curs returns after you hit any key, returning the coordinates of the point, and the key struck as an integer (e.g. `a' as 97 in ascii). In C, all three arguments are pointers, two to float and one to int. sm_defvar defines a variable, it is identical to the interactive command DEFINE name value. It is only used to certain define variables that are significant to SM, currently file_type, graphcap, TeX_strings, x_gutter, and y_gutter. Fortran users might or might not need to double \s in TeX labels, depending on the foibles of your compilers (you are more likely to need the doubled \\ under unix).

    sm_plotsym is like first using the PTYPE { ... } command to define sym, and then POINTS to plot x against y. The sym array consists of triples of integers, (move x y) where move is 1 to move the plot pointer, 0 otherwise. This is not quite the same as the interactive command, but doesn't involve any characters. The move integer may not be omitted. The `file descriptor' that sm_redraw demands will almost always be 0 (standard input) on unix boxes; if it is needed by VMS or other operating systems I will add a note here when I write the device driver. sm_shade shades the inside of the curve specified by x and y if type is 1, or the area below a histogram specified by x and y if type is 2. The line spacing in screen coordinates is delta. You can use sm_toscreen to convert from user to screen coordinates (which run from 0 to 32767, and are used by sm_grelocate). The second pair of arguments should be pointers to int if called from C.

    sm_window takes an extra two arguments over the interactive version; they are the values that you'd specify using the #.# form of window specification; for example, WINDOW 4 5 1 2.3 corresponds to the call sm_window(4,5,1,2,1,3).

    The functions to manipulate 2-dimensional data are described in the next section.

    Function Definitions: 2-D

    The use of the 2-D functions may require a little more explanation. If you want to use SM to read your data, producing contour plots is very similar to doing so interactively, with the difference that instead of defining the variable file_type, you must call the function sm_filetype with the desired value as the argument before reading the data. This is equivalent to calling sm_defvar to define the variable file_type, and is only supported for backwards compatibility. As an alternative, you can fill your own data array, and pass it to SM to be contoured with sm_defimage. The function calls follow (again in fortran), followed by some explanation.

    sm_contour
    Contour current 2-D image
    sm_defimage(arr,x1,x2,y1,y2,nx,ny)
    Define an image (real arr(nx,ny),x1,x2,y1,y2; integer nx,ny)
    sm_delimage
    delete current 2-D image
    sm_filetype(type)
    Set 2-D filetype (character type)
    sm_levels(l,n)
    Set levels for contour (real l(n); integer n)
    sm_minmax(x,y)
    Get minimum and maximum of image (real x,y)
    sm_readimage(file,x1,x2,y1,y2)
    Read a 2-D image (character file; real x1,x2,y1,y2)

    The translation to other languages is not quite as simple as above. In C, sm_minmax expects to be passed pointers to floats, and the first argument to sm_defimage is not a 2-D array, but an array of pointers to the rows of the image. x1, x2, y1, y2 specify the limits as in the interactive IMAGE command; if they are set to be 0.0 then the dimensions of the array will be used. As an example, again in non-VMS fortran:

            integer NXY
            parameter (NXY=20)
            integer sm_device
            integer i,j
            real z(NXY,NXY),lev(NXY)
    c
            do 2 i=1,NXY
               do 1 i=1,NXY
                  x(i,j) = (i-NXY/2)**2 + (j-1)**2
    1          continue
    2       continue
    c
            if(sm_device('postscript latypus') .ne. 0) then
               print *,'Can"t open printer'
               stop
            endif
            call sm_graphics
            call sm_limits(-1.,21.,-1.,21.)
            call sm_box(1,2,0,0)
            call sm_defimage(z,0.,20.,0.,20.,NXY,NXY)
            call sm_minmax(amin,amax)
            do i=1,NXY
               lev(i)=amin + (i - 1.)/(NXY - 1)*(amax - amin)
    3       continue
            call sm_levels(lev,NXY)
            call sm_contour
            call sm_hardcopy
            call sm_alpha
            end
    

    Calling the SM interactive Parser from Programmes

    It is also possible to call the SM parser directly from your own programmes. This is usually done by people who have large data arrays that they want to plot, so we have also provided a call to define your arrays as SM vectors. To use this, your SM guru must build an extra library, (called `libparser.a' on unix systems). This is done with the command

            make Parser
    

    in the top level SM source directory. If this has been done, you can link the parser into your code by including the flag -lparser before the other SM libraries (but after any -L flags). The programmes `interp' and `finterp' in the `callable' directory provide examples of calling SM's parser; `finterp.f' looks like:

          real a(10)
          integer i
    
          do 1 i=1,10
             a(i) = i
     1    continue
    
          call sm_array_to_vector(a,10,'xyz')
          print *,'Calling SM parser...'
          call sm_parser('-q')
          print *,'Exited parser'
          end
    

    Here the array a (with 10 elements) is to be called xyz in SM, and SM is to be invoked with the command line options -q. You can of course define as many vectors as you feel like, but using DELETE to delete them from the parser is a serious error (i.e. don't!).

    sm_array_to_vector(arr,n,name)
    Define an SM vector called name to have the values of the array arr. (real arr(n); int n; character name).
    sm_parser(args)
    Start up the SM command parser as if you had started SM interactively with command line arguments args, but with some vectors predefined (those that you defined using array_to_vector) (character args).

    Why do I have to call sm_alpha, sm_graphics, sm_gflush, and sm_redraw?

    The question sometimes comes up, `Why do I have to bother with these calls sm_alpha, sm_graphics, sm_gflush, and sm_redraw? After all, other graphics libraries don't require them.'

    The answer is that it all depends on how sophisticated and device dependent you want to be.

    If you are using something like an xterm to plot to, then yes, you do need to explicitly switch between graphics and alpha screens -- or else you'll get gibberish on the text screen (something like `?_"s#M"s>H"s#M#b#M!s#F!s#F0"s$X") and no graphics on the graphics screen, or else your graphs and your input commands (and some gibberish to handle the screen editor) mixed together on the graphics screen. Of course the SM routines could have done the switches for you, but if the mode were changed everytime that it might need switching, you'd get an immense amount of mode switching. Why would that matter? Try running the code on a terminal like a vt240 that physically has only one screen.

    If you didn't care about devices like xterm and were determined to only use devices like x11 or sgi or postscript you wouldn't have to worry about any of this.

    Next to gflush. For efficiency, SM doesn't guarantee that every write request is actually written to the screen when received, so at the end of a function call there could be lines or points that haven't yet reached the screen. gflush deals with this by flushing the graphics buffers, thus ensuring that all pending writes to the screen are completed. If you don't care about possible missing points, don't worry about it.

    Finally redraw. On a traditional terminal such as an I^2S or a tektronics 4010, there was no need to worry about other windows appearing on top of your beautiful graphics window and erasing what was there; for some devices such as xterm there is no problem as the terminal emulator is always looking at the screen, like a faithful dog, waiting for you to type something or for one of its screens to get corrupted. In the old days of sunview, Sun had hacked the lowest level system calls (read etc.) to allow an application programme to call the equivalent of redraw automatically (well, under a reasonably common set of conditions). But if you are running on something like an X11 display or a GL display on an SGI, there is no-one looking out for events that erase your window, and you have to do it yourself. If you don't care about refreshing the screen when raised to the top of the window stack, there's no need to worry about redraw.

    So all these complications with alpha, redraw, and friends are due to SM being designed to handle all the possible nasty cases. If you use SM interactively, it is all done for you, but if you want to call SM functions you have to decide how much responsibility you want to take.

    Of course, you can write a trivial wrapper about each SM primitive that calls sm_graphics before calling (e.g.) sm_points, then sm_gflush and sm_alpha just before returning. In a similar way, you could write a general i/o function that calls sm_gflush then sm_alpha, then prompts you, then calls sm_redraw, then reads the reply, then calls sm_graphics. But I don't think that the SM primitives should do it for you.

    How to Choose the Name for Fortran Subroutines

    As discussed above, the names used for SM callable functions from fortran are, in general, different from those used in (almost?) all other languages, as fortran's calling conventions are different, so we have to provide interface code (in the file `sm/src/plotsub/interface.c'). The SM function sm_func may be called as AAAsm_funcBBB where AAA and BBB are the values of the C-preprocessor macros FORTRAN_PREPEND and FORTRAN_APPEND respectively; their default values are nothing and _. If you want to change this, edit sm/src/options.h and define the values that you desire. Note: with C compilers that don't adhere to the C89 ansi standard, you are restricted to the values f and _ for PRE- and APPEND.

    After changing options.h (or options.skl if you want your changes to survive the running of `set_opts'), rebuild SM and you should be in business.

    The SM Grammar

    Previous editions of this manual included a printed copy of the SM grammar, prepared directly from the source code using the get_grammar utility. This was helpful in understanding otherwise bizarre objections to your favoured command, as it specified exactly what SM would accept, but used a lot of paper. We have therefore omitted it from this edition, but you are welcome to make yourself a copy.

    Two-Dimensional Graphics

    There is limited support for 2-dimensional graphics in SM, and we do not expect to make many extensions in this area. Currently it is possible to read an unformatted image, to draw contours, to generate a Floyd-Steinberg dithered image, to extract values from the image into vectors, and to obtain image values with a cursor. Previous editions of this manual (prior to version 2.3) have traditionally said: `We expect to support half-tone imaging in the nearish future'; as of this release there is a macro (called greyscale and written by Gabor Toth here at Princeton) that draws reasonably efficient grey scale images on any supported output device.

    The problem of specifying data formats for images is difficult, and we have adopted an approach of using a `filecap' file to describe the unformatted files. This allows the user to write the file using C or Fortran (or, presumably, lisp) and then use SM to read the data. The name of the entry in the filecap file is given by the variable file_type, which may be given in your `.sm' file if you use the standard startup macro. The format of data on disk is first the x- and y- dimensions of the image, then the data in row-ascending order. The exact statements used to write the data may depend on the chosen value of file_type (or vice-versa). A description of the filecap fields comes at the end of this appendix; most users should never have to change this file.

    It is also possible to read data from a formatted file and create an image inside SM, for example if x and y are integers in the range 0-9:

    IMAGE (10,10)                   # declare a 10*10 array
    READ { x 1 y 2 vals 3 }         # read some data
    SET IMAGE(x,y) = vals           # and put it into the image
    

    The command used to read an image is IMAGE, and you may optionally specify the x and y range covered by the data (e.g. IMAGE datafile xmin xmax ymin ymax; this also works with declarations like the one in the previous paragraph). Only the region of the image lying within the current limits is plotted when contouring - just like any other graphics operation in SM. Images may be deleted with the DELETE IMAGE command. To extract values from an image, use the special expression IMAGE(vec_x,vec_y) which has the value of the image at the points (vec_x,vec_y).

    You can extract variables from the image's header using the command DEFINE name IMAGE. See the discussion of filecap if you need to know what variables can be retrieved this way.

    If you want to use non-interactive SM (i.e. write your own command interpreter), there is a call (defimage) to define your data array to the contouring package, but this is of course not available interactively.

    Filecap

    Filecap is similar in graphcap or the Unix termcap, and in fact the `filecap' entries may be physically in the same file as graphcap (i.e. added as if they were graphcap entries; of course an attempt to say device fits will lead to confusion...). Filecap can be specified as a list of files to be searched in order. The fields in filecap are used to specify the data type of the file, the record format of the file (record-length, inter-record gaps, etc.) and the header used (length of header, offset of desired items). Note that these are `unformatted' files, as written by C write() or fortran write(num) statements.

    The current filecap is as follows:

    #
    # Filecap describes unformatted file formats to SM. The syntax is
    # identical to termcap or graphcap files.
    #
    c|C|C files:\
            :HS#8:nx#0:ny#4:
    ch|CH|C files with headers:\
            :HS#24:nx#0:ny#4:x0#8:x1#12:y0#16:y1#20:
    fits|cfits|FITS|CFITS|C FITS files:\
            :DA=fits:RL=2880:
    no_header|Files with no header, will prompt for nx, ny:\
            :HS#0:
    unix|UNIX|Fortran unformatted files under Unix on a Vax:\
            :HS#-1:nx#0:ny#4:RS#4:RL#-1:RE#4:
    unix_int|UNIX_INT|Like unix, but integer*4:\
            :DA=int:tc=unix:
    unix_short|UNIX_SHORT|Like unix, but integer*2:\
            :DA=short:tc=unix:
    vms_var|VMS_VAR|Fortran unformatted under VMS, recordtype=variable:\
            :HS#8:nx#0:ny#4:
    vms_fixed|VMS_FIXED|Fortran unformatted under VMS, recordtype=fixed:\
            :HS#-1:RS#0:RL#-1:REnx#0:ny#4:
    # This seems to be correct, based on one example
    vms_direct:VMS_DIRECT|Fortran unformatted, direct access, under vms:\
            :HS#8:RS#0:RE#0:nx#0:nx#4:
    

    Comment lines start with a #, continuation lines start with white space (a tab or a space) and \ may be used to continue an entry on to the next line. The first few fields in an entry are separated by |, and are alternative names for the same entry. For example, fortran unformatted files under Unix may be referred to as unix or UNIX. Fields are separated by colons, and are of the form :CC#nnn: for numbers, and :SS=str: for strings. Omitted fields may be specified as :CC@nnn:, or simply omitted. Filecap capabilities currently used are:

    DA (DAta type)
    Type of data in file (string)
    FS (File Start)
    Unwanted bytes at start of file
    HS (Header Size)
    Size of header
    RE (Record End)
    Unwanted bytes at end of record
    NS (No Swap)
    Don't swap bytes for FITS files
    RL (Record Length)
    Number of useful bytes per record
    RS (Record Start)
    Unwanted bytes at start of record
    SW (SWap)
    Byte swap 2 by integer data, and byte-and-word swap 4 by integers.
    nx (Number X)
    Offset in file of X size of data
    ny (Number Y)
    Offset in file of Y size of data
    x0 (X 0)
    Coordinate at start of X axis
    x1 (X 1)
    Coordinate at end of X axis
    y0 (Y 0)
    Coordinate at start of Y axis
    y1 (Y 1)
    Coordinate at end of Y axis

    In addition, HH is supported as an archaic form of HS.

    In terms of these quantities a file will look like this: As mentioned below, HS can be negative and is then taken as the record length of the header RL_H, in which case the file will be like: Note that the first real data record begins with an RS -- this means that if you are writing fortran you must write the header in a different write statement than the one that you use to write the data (i.e. write(fd) nx,ny,arr will not work).

    Most parameters are optional, and will default to 0. If RE or RS is specified, you must give RL as well. If you specify it as negative, then we'll look for it from the operating system. You must provide a value for HS (or HH if you're old fashioned), if it is negative we'll assume that even the header has a record structure, with RS and RE just like any other record. Its record length will be taken to be RL, if RL is positive, otherwise we'll find it from the operating system. If HS and RL are both negative there is no reason why the record length of the header should be the same as that of the data.

    If neither nx nor ny are present in the graphcap entry you will be prompted(31) for the x and y dimensions of the file, otherwise they must both be present. If they are negative they are taken to be the negation of the true dimension (so a filecap entry :ny#-6: specifies that the y-dimension of the data is 6), but usually they are taken as the offsets of the values of the x and y dimensions in the file, relative to the start of the header (if you set HS to be zero you can specify nx and ny on the command line, but they must be on a separate line, either a real separate line, or following a \n). Note that HS excludes FS, so HS will usually be 2*sizeof(int) irrespective of the value of FS, and nx will usually be 0. If HS is negative, then the nx and ny offsets also ignore RS, i.e. nx is still usually 0.

    As an alternative to specifying the actual file sizes as negative integer values of nx or ny (e.g. :nx#-10:) you can simply specify them as positive string values: :nx=10:. Note how this differs from :nx#6:.

    Possibilities for DA are char, float (default), int, long, and short, all as in C, and also fits for FITS format data. (If you don't know what FITS format is, don't worry about it. It's a style of header and record structure used for data transport in astronomy(32). If you do know about FITS, then we assume that each record is 2880 by long, although possibly with RS and RE non-zero. The header is processed for the size of the file and the value of BITPIX. If the file is not SIMPLE, it is taken to be 4 by floating point data. CRPIX, CRVAL, and CDELT are interpreted correctly, as are BZERO and BSCALE. X0, x1, y0, and y1 are specified as the keywords X0, X1, Y0, and Y1 respectively, and take preference over CRPIX etc. FITS files on a machine with vax byte order are supposed to be byte swapped, but you can override this by specifying the NS capability which stops SM from doing any byte swapping). If you specify SW it takes preference over any other instructions about byte swapping and forces byte-swapping for 2 byte (short) data, and byte-and-word swapping for 4 byte (int) data. If x0 and x1, or y0 and y1 are omitted, the range of values on the appropriate axis is taken to be 0 to nx-1 (or ny-1). You can override these values with the IMAGE command. The values of X0 (and so on) can be obtained directly using DEFINE X0 IMAGE; Other values can be specified in `filecap', for example a filecap entry :aa#24: specifies that DEFINE aa IMAGE should recover the (floating-point) number stored at byte offset 24. Because of the way that filecap files work, you are restricted to two-character names. If you are using FITS files, all the keywords from the header are available, but you should remember that SM is case sensitive.

    For example, suppose I wrote a file using the Unix f77 compiler, with some code that looked like:

            integer *4 nx,ny,arr(10,20)
    c
            nx = 10
            ny = 20
            write(8) nx,ny
            write(8) arr
    

    (Omitting opening unit 8 as an unformatted file, and filling the array with data). Then I could read it in SM by defining file_type to be unix_int. The filecap entry indicates that the length of the header is to be obtained from unix (in fact from the file, it'll be the record length of the header), as is the record length. Both the start-of-record (RS) and end-of-record (RE) gaps are 4 by long (they in fact contain the record length, but you needn't know that). The number of x-records is at zero offset in the file, and y-records is at offset 4. In other words, allowing for FS being 0 and RS being 4, nx occupies bytes 4-7 in the file, and ny 8-11. The data is taken to be 4-byte integers (integer*4 to fortran). In fact, the first record consists of only the values of nx and ny, so the record length of the first record is 8 by, and part of an equivalent filecap entry would be

    :FS#4:HS#12:
    

    where I have interpreted RS as FS, and added the RE onto the end of the header length HS. If I had written the data out line-by-line in a do loop, the file type would still be unix_int, but the record length actually used by SM in reading the file would be different (see section Image).

    As another example, I use an image processing system called Wolf that used to use files with the arcane structure of 200 bytes of header, then the x- and y-size of the file, given as ints, then more header up to a total offset of 1024 bytes from the start of the file, then the data written as short integers. I could write a header with code that looked somewhat like (omitting all error checking):

            int fd,i;
            int xs = 20,ys = 10;
            short arr[10][20];
            ...
            lseek(fd,200L,0);
            write(fd,(char *)&xs,sizeof(int);
            write(fd,(char *)&ys,sizeof(int);
            lseek(fd,1024L,0);
            for(i = 0;i < ys;i++) write(fd,(char *)arr[i],xs*sizeof(short));
    

    The corresponding filecap entry is

    wolf|Wolf-IfA files:\
            :HS#1024:nx#200:ny#204:DA=short:
    

    which is pretty simple really.

    Termcap -- A Terminal Database

    Termcap is a Unix idea, that makes it possible to write software that runs on a wide range of different terminals. The implementation that SM uses contains no Unix source code. The idea is similar to graphcap or filecap -- each property of the terminal is given as a field in a file. For example, the string to clear to end of line is called ce, and for ansi terminals is given as ce=^[[K. The format for termcap files is described under `graphcap', which is identical. Our termcap is identical to the Unix one, so for details see section 5 of the Unix manual. The file to use as a termcap file is specified as termcap in your `.sm' file, or as the environment variable TERMCAP (logical variable to VMS). If SM can't open TERMCAP as a file, it is taken to be the value of the entry for your terminal. If it doesn't begin with a colon, it is treated like a termcap file, and searched for the desired terminal. If it does begin with a colon, it is simply taken to be the termcap entry to use. A TERMCAP variable takes precedence over an entry in your `.sm' file. As for graphcap, a list of termcap files can be specified, to be searched in order.

    A full termcap entry can be pretty long, but fortunately SM only uses a small subset. (The rest are needed by full scale screen editors, but we haven't written one.) In particular we use:

    ce (Clear End)
    Delete to end of line
    ch (Cursor Horizontal)
    Move cursor within line
    cm (Cursor Movement)
    Move cursor around screen
    cn (ColumNs)
    Number of columns on screen
    dc (Delete Character)
    Delete character under cursor
    is (InitialiSe)
    Initialise terminal
    ks (Keypad Start)
    Initialise Keypad
    ks (Keypad End)
    Undo Keypad Initialisation
    kd (Kursor Down)
    Move cursor down one character
    kl (Kursor Left)
    Move cursor left one character
    kr (Kursor Right)
    Move cursor right one character
    ku (Kursor Up)
    Move cursor up one character
    k1 (Key 1)
    Key sequence emitted by PF1 key (also k2, k3, and k4)
    li (Lines)
    Number of lines on screen
    pc (Pad Character)
    Pad character, if not NIL

    All of these are strings except co and li which expect numbers, and pc which expects a single character. The editor uses all of these except co, although the k ones are only provided as a convenience to you, mapping the arrow keys. In fact, these arrow keys may supersede desired bindings such as ^K to delete to end of line (e.g. on a televideo 912). If this happens, use the EDIT or READ EDIT commands to fix things up.

    The cursor addressing strings ch and cm use a variant of the C printf %. Consider the string as a function, with a stack on which are pushed first the desired column, and then the desired line (so the line is on top). The possible % formats operate on the stack, and pop it after output.

    d
    As in printf, zero origin
    2
    Like %2d
    3
    Like %3d
    .
    Like %c
    +x
    Add x to value, then `%.'
    >xy
    If value > x add y (no output)
    r
    Interchange line and column (no output)
    i
    Increment line and column by 1 (no output)
    %
    Output a single %

    We do not support %n, %B, or %D out of pure laziness. A couple of examples might help. To go to (5,60) a vt100 expects to receive the string ^[[5;60H, so the termcap entry reads :cm=\E[%i%d;%dH:. We need the %i to go to one-indexed coordinates, and the escape is written \E. The :'s delimit the termcap entry. A Televideo 912 expects to be given first ^[=, then the coordinates as characters, where ` ' is 0, `!' is 1, and so forth through the ascii character set. To convert a given number to this form we add ` ', so the termcap entry is :cm=\E=%+ %+ :.

    If an entry begins with a number, it gives the number of milliseconds of padding that the terminal requires (it is optionally followed by an *, which we can and do ignore). If the terminal requires a pad character that is not simply ascii NIL it should be given as pc, so to use `a' as a pad character specify :pc#97:. The baudrate is taken to be 9600, unless you specify it as the ttybaud variable in your `.sm' file. We have never yet seen SM have any trouble with padding, so we wouldn't worry about any of this if we were you.

    You might wonder why we need both ch and cm. The simple answer is that we don't, but that we do want one of them. Because cm moves the cursor to a specific line, its use requires that SM remember which line you are on which can be tough what with switching to and from graphics mode. We therefore assume that you are on the last line of the terminal, as set by li or explicitly by the TERMTYPE command. If you provided ch this problem wouldn't arise but many terminals don't support such a capability and we have to code around this deficiency. To summarise: use ch if you can, failing that use cm.

    There is a problem with using the last line of the screen as the SM command line, and this is that some terminals use the same screen for graphics as for text, and your graph will merrily scroll away as you type. The graphcap GD (Graphics Disable) command can be set to take you to the top of the screen, but cm won't keep you there. Your only chance is to disable cursor motion, either by setting ch to `disabled', or by specifying a negative screen size to TERMTYPE which has the same effect. You can still use the editor, but it'll be slower. If you still have trouble, try TERMTYPE dumb, which really is a bit brain damaged, or TERMTYPE none which disables the editor entirely.

    New Devices and New Machines

    Adding New Devices

    As we have discussed extensively (see section The Stdgraph Graphics Kernel), most plotting devices can be accommodated within the framework of stdgraph/graphcap without writing a line of C; raster devices can use the raster device driver. Sometimes, however, this is not possible and you must write a real driver. An example would be making SM run native under X11; running under a terminal emulator can be done with graphcap.

    If you do make any changes, write any new drivers, or modify graphcap (except trivially) please send us copies of your modifications.

    Let us assume that you really do need a new driver, how do you go about it? SM communicates with devices solely through a graphcap entry, some external variables (defined in sm.h) and a structure called DEVICES which is defined in devices.h and declared in plotsub/devices.c.

    When you issue a DEVICE devname args command, the stdgraph driver is called with the string devname args, and it opens the graphcap entry for devname. If it finds an entry of the form :DV=driver: it tries to find a hardcoded device driver called "driver" (you'll see in a moment where the names come from). If it can find such a driver it calls its setup function (to be defined in a moment) with args as an argument. Your device driver is now in charge, and no more stdgraph routines are called until your device is closed; after you return from your close function stdgraph's close function is called to close the graphcap descriptor. What do you have to remember from this paragraph? That you need a graphcap entry, if only a trivial one along the lines of

    my_device|all_mine:DV=my_driver:
    

    which associates the driver my_driver with the device referred to as either my_device or all_mine. Because stdgraph's stg_open and stg_close functions are called around your device code the standard graphcap support for OW, IF, DC, OF, SY, and CW is automatically provided if you want it; see the imagen driver and graphcap entries for an example. You might also want to remember that you can directly access any graphcap entry for your device from within your driver, using the functions ttygets, ttygetr, ttygeti, and ttygetb.

    The current definition of DEVICES looks like this:

    typedef struct {             /* output device dependent functions */
       char d_name[40];           /* name of device */
       int (*dev_setup)();        /* initialisation  */
       void (*dev_enable)(),      /* enable graphics */
       void (*dev_line)(),        /* draw a line from x1, y1 to x2, y2 */
       void (*dev_reloc)(),       /* relocate current position */
       void (*dev_draw)(),        /* draw a line from current pos to x,y  */
       int (*dev_char)(),         /* hardware characters  */
       int (*dev_ltype)(),        /* hardware line type */
       int (*dev_lweight)(),      /* hardware line weight */
       void (*dev_erase)(),       /* erase graphics screen  */
       void (*dev_idle)(),        /* switch from graph to alpha mode */
       int (*dev_cursor)(),       /* cursor display */
       void (*dev_close)(),       /* close device */
       int (*dev_dot)(),          /* draw a single pixel dot */
       int (*dev_fill_pt)(),      /* draw a filled point */
       int (*dev_fill_polygon)(), /* draw a filled polygon */
       void (*dev_ctype)(),       /* set colour of line */
       int (*dev_set_ctype)(),    /* set possible colours of lines */
       void (*dev_gflush)();      /* flush graphics */
       void (*dev_page)();        /* start a new page */
       void (*dev_redraw)();      /* redraw the screen, if need be */
    } DEVICES;   
    

    Note that this is the DEVICES structure at the time of writing; be sure to see that it is still correct when you write your driver! To add a new device to SM, all you have to do is to write functions to fill as many of the slots in DEVICES as possible, add their definitions to `sm_declare.h', then add an element to the array devices[] in devices.c. After recompiling, SM will recognise your device by the name d_name that you gave it in devices[]. Traditionally the functions have the same name as those given above with the dev replaced by some mnemonic for your new device. You should surround both your driver and the entry in devices[] with #ifdef's so that all I have to do to totally lose your device is to not #define it to the compiler. In writing your device you may want to use graphcap to give it various pieces of information. This can be done; see the imagen driver for an example.

    You should include the file `sm_declare.h' (which automatically includes `options.h') which provides declarations for all SM functions (including prototypes if the compiler supports them), and you should add your own declarations to this file.

    If you don't provide some of the functions, SM will try to emulate them in its software. For example, of your ltype function returns -1, then we will chop your lines for you. There are a set of functions for nodevice that have the correct types, you can use them for functions that you don't want to provide (e.g. if you don't support dev_ltype you can use no_ltype). Some of the routines, e.g. dev_char are sometimes passed NULL arguments to inquire if they can provide particular capabilities. In the following paragraphs we discuss all the functions, their arguments, what they do, and what they return.

    All coordinates are given in screen units, where the device is 32768 pixels along a side.

    dev_setup(str) (char *str;)
    Open the plotting device. Str contains the rest of the command line, so if the DEVICE command was DEVICE newdev abcde zyxwvut, setup will be passed abcde zyxwvut. If the device can't be opened for some reason setup should return -1, otherwise 0. Setup has a number of book-keeping responsibilities: setting the value of termout to 1 if the device is a terminal, otherwise 0; setting the value of ldef to the maximum spacing between lines if they are to appear as one thick line rather than a set of parallel ones, (used if we have to emulate LWEIGHT); setting the variables xscreen_to_pix and yscreen_to_pix to give the conversion from SCREEN to device coordinates; calling default_ctype with the name of the default colour for lines (e.g. default_ctype("white")). There is no need to look in the `.sm' file for a foreground entry, this is done for you (by set_dev()) after dev_setup returns, but you should deal with any background entry if you feel so inclined. There is a function parse_color that converts a colour name into r, g, and b values (see the sunwindows setup function for an example of its use) that may help. Note that you should not call stg_setup -- this was done for you automatically before your setup function was called. No return value.
    dev_enable()
    Enable plotting on the device. For a graphics terminal this means switching from alpha to graphics mode, but may well not be required for other devices. No arguments, no return value.
    dev_line(x1,y1,x2,y2) (int x1, y1, x2, y2;)
    Draw a line from (x1,y1) to (x2,y2). No return value.
    dev_reloc(x,y) (int x,y;)
    Move the plot marker to (x,y). No return value.
    dev_draw(x,y) (int x,y;)
    Draw a line from the current plot marker to (x,y) which becomes the new plot marker. No return value.
    dev_char(str,x,y) (char *str; int x,y;)
    Write the string at the position (x,y). The position given is just to the left of the first character, at about the height of the centre of a capital letter. Return 0 if you support hardware characters, otherwise -1 in which case we'll use the software character set instead. If str is NULL, this is just an enquiry as to whether the device supports a hardware character set. Char is also called (with NULL) whenever the value of expand or angle is changed, to allow you to adjust the sizes of cheight and cwidth (the height and width of a character in SCREEN units) for a hardware character set, as the sizes of hardware sets can be very different from the SM fonts. You should only change cheight and cwidth if we are really going to use your hardware characters (expand == 1, angle == 0).
    dev_ltype(i) (int i;)
    Set the LTYPE to be i, as in the interactive command. Return 0 if you support the ltype asked for, otherwise -1. If you can't support a linetype, make sure that you are drawing solid lines, so that SM can emulate it for you. LTYPE 10 is special, it is generated by the LTYPE ERASE command, and asks you to delete lines as they are drawn, LTYPE 11 is used to indicate the end of LTYPE ERASE mode.
    dev_lweight(lw) (real lw;)
    Set the LWEIGHT to be lw, as in the interactive command. Lines with lweight 0 should be the natural thickness of a line on your device, higher lweight lines are usually drawn with a thickness of LDEF*lweight in SCREEN coordinates. Return 0 if you support the lweight asked for, otherwise -1.
    dev_erase()
    Erase the screen. No return value.
    dev_idle()
    Return to alpha mode, the opposite of dev_enable(). No return value.
    dev_cursor(x,y) (int *x,*y)
    Find the current position of the cursor, if there is one. If there is, the return value is the character struck by the user, otherwise return -1. If your device has a mouse, you should try to make the buttons correspond to `e', `p', and `q' from left to right. If you only have one button, `p' is probably the best choice.
    dev_close()
    Close the device, the opposite of dev_open(). Note that you should not call stg_close -- this is done for you automatically. No return value.
    dev_dot(x,y) (int x,y)
    Draw a dot at (x,y), (i.e. a real dot, not like the DOT command). You'll have to do the move to (x,y) yourself. Return 0 if you can draw dots, otherwise -1.
    dev_fill_pt(n) (int n;)
    Draw a filled point at the current position. This is the routine that draws PTYPE n 3 points. Remember to allow for EXPAND and ANGLE. Return 0 if you can draw the point, otherwise -1;
    dev_fill_polygon(n) (int style; float *x, *y; int n;)
    Draw a filled polygon defined by x and y (a total of n) points) in the style style (0: filled; all other values are currently undefined). Return 0 if you can fill polygons in the desired style, otherwise -1. You may get queries with on style with n == 0.
    dev_ctype(r,g,b) (int r,g,b;)
    Set the current line colour. The interpretation of (r,g,b) depends on whether you have a dev_set_ctype function. If you don't, then r, g, and b are the red, green, and blue intensities in the range 0-255. If you do, then r is an index into the array defined by set_ctype and g and b are irrelevant. No return value.
    dev_set_ctype(col,n) (COLOR *col; int n;)
    Define a set of colours to be accessed by set_ctype. Col is an array of n elements, each of which consists of 3 unsigned chars (COLOR is defined in mongo.h) corresponding to red, green, and blue in the range 0-255. Ctype will be used to access this array to change colours. Return 0 if you do support set_ctype, otherwise -1. If col is NULL this is just an enquiry. If you are asked for more colours than you are prepared to support, you should scale the request in a user transparent way (e.g. if she wants 256 colours, and you are only giving her 128, you should arrange that dev_ctype divides her requests by 2 so it looks as if she got all 256).
    dev_gflush()
    Flush graphics, update graphics on screen. No return value.
    dev_page()
    Start a new page. If your device produces hardcopy you should print the current page and start a new one; if you are writing a window system driver you probably should simply raise the window so that it is visible. No return value.
    dev_redraw(fildes) (int fd)
    Redraw the screen, but only if it needs it. This function is called by get1char() before it tries to read a character from SM's standard input (file descriptor fd). It is intended for the use of drivers that need to keep an eye on `their' window. For example, the Silicon Graphics driver needs to redraw a window every time that it is moved. Such device drivers will probably want to poll fildes (e.g. using select()) to see that there really is input before returning; look at the sgi or x11 driver if you want an example. No return value.

    If you are still confused, look at some of the hardware drivers that are already available.

    Porting to New Machines

    Porting to new Unix machines should be relatively simple. If the machine runs BSD Unix the only changes that should be necessary are to the exception handler routines in main.c, to reflect the error conditions signalled by your new machine. For a Sys V Unix, you'll have to edit `options.h' to define SYS_V. Otherwise, your only problems should be hidden bugs which flourish in new architectures.

    Otherwise there isn't all that much that I can tell you. SM runs on Unix (BSD and SysV) and VMS machines, and the places where the code is #ifdef'd for those operating systems is your best bet for places where there are machine dependencies. That said, there are a few obvious problem areas.

    First consider get1char, which is the routine that reads terminal input. It has to be able to get one character a time from the terminal, and not interpret control characters. This is obviously operating system dependent (CBREAK under 4.3BSD, PASSALL under VMS, ...). Get1char expects to be passed ^A to start operations, and EOF to end them. Anything else means `return a character please'. The terminal output functions used by stdgraph are also machine dependent, for example they use QIO calls under VMS.

    As mentioned above, the exception handlers try to interpret the exceptions that they receive and this may require a little modification in main.c.

    Hardcopy devices need the system() system call to send commands to the operating system, and if you don't have one you are in trouble.

    In a few places SM needs to make assumptions about how file names are put together, and these will need changing. On a similar note, some operating systems (e.g. VMS) are very picky about opening files and you may have to be careful. Note the use of the DT graphcap capability to signal particular requirements to the programme.

    A machine that didn't use ascii would be rather a nuisance as makeyyl assumes that the characters for A-Z are contiguous, and although this could be easily fixed I am not sure that other problems wouldn't arise.

    A final rather horrifying thought is that Bison might not compile on a machine that doesn't have YACC as an alternative. I don't know how to fix that, you'd just have to hope for the best, or else run Bison/YACC on a different machine and copy over control.c.

    The System Macro Libraries

    This appendix describes the collection of useful macros, written by a variety of people, that are to be found in the default macro directory, i.e. the directory pointed to by the macro entry in your `.sm' file. If ever you write a useful (or simply clever) macro, why not send it to us for inclusion in the next version of SM?

    The macros are arranged in a number of files which may be read using the load macro. For example, to load the file fonts type load fonts. To forget a set of definitions, use unload. Under Unix, there is a macro lsm that can be used to list the contents of macro libraries, e.g. lsm utils. It is more-or-less complete depending on the value of VERBOSE, just like LIST MACRO.

    The list that follows gives the name and a one-line synopsis of all the macros in the default files as of the date of this manual. The full text of any macro may be examined via the help <macroname> command in SM; the default files may be printed for those who desire a hardcopy.

    file `abbrev' in directory `macro' (This is a file of all unambiguous abbreviations of keywords. It is created using the shell script `abbrev' in the main SM directory. Its use may interfere with cunning macros, and is not recommended).

    file `cover' in directory `macro'

     cover       # draw the cover
    

    file `default' in directory `macro'

     batch       ## run the history buffer, but don't delete from history list
     bell        ## ring terminal bell
     calc        ## evaluate an expression
     cd          ## change directories
     compatible  ## define macros to be compatible with Mongo
     declare     ## declare a vector $1: declare name size
     del         ## delete last command from history list
     del1        ## don't put command on history list
     echo        ## write to terminal
     ed          ## edit a macro, or the previous one if no argument
     edit_all    ## edit history buffer
     emacs_all   ## edit the history list using an external editor.
    emacs_hist  ## edit the history list using an external editor.
     error_handler ## Handle ^C interrupts
     execute     # read and execute an SM file of commands (1 per line)
     extend_history # Extend history buffer to be of size $1
     for         # Repeat a macro while a condition is true, like C's for(;;)
     gripe       ## complain to Robert (Unix only)
    h           ## get help
     head        ## print the top of the current data (or other) file
     hm          ## help with the last macro edited
     hv          ## help with variable
     insert      ## insert text after line $1
     load        ## load macros in default directory
     load2       ## load macros in (second) default directory
     ls          ## list macros
     lsm         ## list macros in a file in default macro directory
     lsv         ## list variables
     q           ## check, then quit
    re          ## macro read
     reset_ctype # Reset the default ctypes (except "default")
     repeat      # Repeat a macro `name' while the condition is true
     sav         ## save to a file $1, don't save from files `$mfiles'
     show        ## show current values of various things
     startup     ## macro invoked upon startup
     undef       ## undefine a variable
     undo        ## undo [macro] : undo (i.e. erase lines drawn by) macro $1
     unload      ## forget macros from a file
    v           ## set verbosity
    wr          ## macro write
    

    file `demos' in directory `macro'

    square      # sum the Fourier series for a square wave, using $1 terms
    colours     # draw a circle in a number of colours until ^C stops it
    crings      # draw a set of coloured circles
     gauss_convolve # convolve 2 Gaussians with sigmas $1 and $2
     grey_sincos # draw a grey scale image of a sin(x)cos(y) surface
    scribble    # use cursor to draw a line, and then shade the interior
    shading     # draw an ammonite
     sundial     # draw a sundial, allowing for the analemma
    

    file `fonts' in directory `macro'

     fonts       # draw the font table
     TeX_defs    # draw the "TeX" definitions
     make_char   # help create a new character
    

    file `fourier' in directory `macro'

    #
    # Macros to make dealing with complex numbers for FFT's easier
    # Assumes that complex vector `name' is represented by two vectors
    # called name_r and name_i
    #
     fft         # Direct FFT: fft name name
    ifft        # Inverse FFT: ifft name name
    cadd        # Add complex numbers: $1 = $2 + $3
    cdiv        # Divide complex numbers: $1 = $2/$3
    cmod        # Modulus: $1 = |$2|
    cmult       # Multiply complex numbers: $1 = $2*$3
    csub        # Subtract complex numbers: $1 = $2 - $3
    imag        # Imaginary part: $1 = Im($2)
    real        # Real part: $1 = Re($2)
    vcentre     # shift a vector so that its 0th element appears in the middle
    vcenter     # an alias for vcentre
    

    file `images' in directory `macro'

     hist_equalise # Histogram equalise an image
     gamma       # Gamma correct an image
    

    file `irregular' in directory `macro' These macros were written by Gabor Toth, to whom many thanks. They enable you to define an SM IMAGE from 3 vectors, (x,y) and the value

     irregular_image # Set IMAGE from 3 vectors (x,y) and z
     regulargrid # Determine x and y coordinates for an nx*ny enclosing grid t
     triangulate # Determine regular grid & interpolation weights
     trigrid     # Interpolate to the regular grid
     trigridtry  # Demonstrate the use of trigrid
    

    file `math' in directory `macro'

    #
    # Written by Daniel Pfenniger (PFENNIGER@obs.unige.ch)
    # `A&S' is Abramowitz & Stegun
    #
     J0          # Bessel func. J0(|x|), A&S, 9.4.1,.3
     Y0          # Bessel func. Y0(|x|), |x|>0, A&S, 9.4.2-3
     J1          # Bessel func. J1(|x|), A&S, 9.4.4,.6
     Y1          # Bessel func. Y1(|x|), |x|>0, A&S, 9.4.5-6
     I0          # Modified Bessel func. I0(|x|), A&S, 9.8.1-2
     I1          # Modified Bessel func. I1(|x|), A&S, 9.8.3-4
     K0          # Modified Bessel func. K0(|x|), |x|>0, A&S, 9.8.5-6
     K1          # Modified Bessel func. K1(|x|), |x|>0, A&S, 9.8.7-8
     K           # Complete elliptic integral K(m), 0<=m<1, A&S 17.3.34
     E           # Complete elliptic integral E(m), 0<=m<=1, A&S 17.3.36
     fresnel     # Calculate the Fresnel integrals C, S, and F
    

    file `matrix' in directory `macro'

     matrix       # Introduction to matrix utilities
     mdeclare     # declare $1 to be a $2x$2 square matrix	
     mdimen       # print matrix $1's dimension
     mprint       # print the matrix $1  (given as $1_0 $1_1 etc -- see minv)
     madd         # set matrix $1 equal to $2+$3
     minv         # Quick matrix inversion, done in place.
     mset         # set matrix $1 equal to $2
     mmult        # set matrix $1 equal to $2*$3
     mmult_c      # set matrix $1 equal to $2*$3 where $3 is a scalar
     mmult_v      # set vector $1 equal to $2*$3 where $3 is a vector
     mmult_vT     # set vector $1 equal to $2^T*$3 where $2 is a vector
    

    file `mongo' in directory `macro' (omitting those that are simply abbreviations, like ang)

     da          ## set data file
     dev         ## set device
    dra         ## draw, accepting expressions
     ecolumn     ## define vector error_col as error vector
     end         ## quit, not on history
     era         ## erase screen, not on history
     lis         ## list history, not on history
    hard        ## make a hardcopy of what you type (or get by history)
     hardcopy    ## close the old device and set dev type to 0
     hcopy       ## hcopy [printer] [l1] [l2]: Make hardcopy of playback buffer
     hmacro      ## hmacro [macro] [printer]: make hardcopy of `macro' on `printer'
     identification ## write an id to the top right hand corner of screen
     input       ## execute an Mongo file
     mongo       # make SM resemble Mongo as closely as possible
    pcolumn     ## set point column (Mongo)
    playback    ## define "all" from buffer, and run it
     read_all    ## read a macro file, putting `all' onto the history buffer
     read_hist   ## read history from a file
     read_old    ## read an Mongo file onto the history buffer
    rel         ## relocate, accepting expressions
     save_all    ## write the playback list to a file (use sav instead)
    terminal    ## device
     toplabel    ## put label at top of plot
     xcolumn     ## read a column into vector x (Mongo)
     xlogarithm  ## take log of vector x (Mongo)
     ycolumn     ## read a column into y
     ylogarithm  ## take log of y
    

    file `project' in directory `macro' Miscellaneous projections (e.g. equal area). Contributed by Michael Strauss.

     aitoff          #convert l and b to x y in aitoff coordinates: 
     aitoff1         # Convert l and b to x y in reverse aitoff coordinates: 
     aitoffdec       # Draw a line of constant declination in an aitoff plot
     aitoffdec1      # Draw a line of constant declination in a reverse aitoff plot
     aitoffgrid      # Put up equal area grid in Aitoff
     aitoffgrid1     # Puts up equal area grid in Aitoff. Reverse sense
     calc_lb         # Calculates r, l and b given x, y, and z
     circle_exclude  # After circleplot, draws lines corresponding to
     circlelabel     # Draw axes for a redshift circleplot
     circleplot      # Plot redshift points; requires circlelabel to have
     eqgal           # Usage: ra dec l b; Convert (ra, dec) to (l,b). 
     findbreak       ## Sort x y so that xp, yp are continuous
     galeq           # Usage: galeq l b ra dec. Convert (l,b) to (ra, dec)
     galgrid         # Puts up equal area grid
     galplot         # Usage: galplot ra dec l b
     galpoints       # Plot points on an equal area grid; galpoints l b
     gstrans         # Convert supergalactic to galactic coordinates
     hemiconvert     # Convert from l b to x y for hemisphere plotting
     hemidec         # Draw a line of constant declination on a pair of
     hemiplot        # Sky plot in two hemispheres, equal area
     invaitoff       # Convert x y in aitoff coordinates to l, b
     pielabel        # Draw axes for a redshift pieplot
     pieplot         # Plot redshift points; requires pielabel to have
     radtick         # Put tick marks on a radial axis
     sgtrans         # Convert galactic to supergalactic
    

    file `stats' in directory `macro'

     cgauss      # evaluate a Cumulated Gaussian : N($mean,$sig)
     chisq       # Return chi^2 distribution of vector $1 with $2 dof 
     draw_KS     # Draw a cumulated curve, for looking at KS statistics
     erfc        # calculate complementary error function erfc($1)
     factorial   # Use Stirling's formula to calculate a factorial ($1)!
     gauss       # evaluate a Gaussian : N($mean,$sig)
     gaussdev    # return a N(0,1) random vector
     linfit      # linear least squares fit for any number of parameters
     log_fac     # Use Stirling's formula to calculate a log factorial.
     lsq         # do a least squares fit to a set of vectors
     lsq2        # do a least squares fit to a set of vectors, errors in x and y
     prob_KS     # probability of getting a given value of the K-S statistic
     prob_wilc   # return probability in $$2 that x exceeds $1 from Wilcoxon
     rxy         # find Pearson Correlation Coefficient for two vectors 
     smirnov1    # calculate 1 sided Kolmogorov-Smirnov statistic for vector
     smirnov2    # calculate 2 sided Kolmogorov-Smirnov statistic for vectors
     spear       # calculate Spearman rank correlation coefficient for 2 vectors
     stats       # stats vector mean sigma kurtosis : calculate $mean $sigma etc
     stats2      # stats vector weights mean sigma kurtosis
     stats_med   # stats_med vector median SIQR : calc $median $SIQR from vector
     wilcoxon    # calculate Wilcoxon statistic for 2 vectors
     wlsq        # do a weighted least squares fit to a set of vectors
    

    file `trigd' in directory `macro' Equivalents for all SM trig functions, taking degree arguments. Names add an extra `d' (e.g. sind)

    file `utils' in directory `macro'

    alpha_poi   # alpha_poi x y z. Like poi x y, but use z as labels for points
     arc         # the arclength along the curve ($1,$2), e.g. set v=arc(x,y)
     arrow       # use the cursor to define an arrow.
     barhist     # draw a bar histogram
     boxit       # use the cursor to define a box, and draw it
     circle      # draw a circle, centre ($1,$2) radius $3
     cumulate    # find the cumulative distribution of $1 in $2
     draw_arrow  # draw an arrow from ($1,$2) to ($3,$4)
     draw_box    # draw a box, defined by two corners
     error_x     # draw x-error bars: error x y size
     error_y     # draw y-error bars: error x y size
    get_hist    # get_hist input output-x output-y base top width
     gauss       # evaluate a Gaussian : N($mean,$sig)
     get         # syntax: get i j.  Read a column from a file
     glevels     # Set grey levels. Usage: glevels expr
     greyscale   # Draw a grey-scale image.
                #                    Usage: greyscale [npx npy maxweight dmargin]
     info        # Get help about a command from SM's info files
     interp      # Linearily interpolate $3 into ($1,$2), giving $4
     interp2     # Linearily interpolate $3 into ($1,$2), giving $4
     is_file     # Return true if file $1 exists
     is_macro    # Return true if $1 is a macro
     is_set      # define variable $$1 if the $3'rd bit is set in $2
     is_vector   # Return true if $1 is a vector
     logerr      # syntax: logerr x y error, where y is logged, and error isn't
     mconcat     # Concatenate 2 macros, optionally renaming result
     modulo      # find $1 modulo $2
     number      # convert a string vector to an arithmetic one
     pairs       # pairs x1 y1 x2 y2. connect (x1,y1) to (x2,y2)
     polar       # draw a circle as an `axis' for polar coordinates
     pmatrix     # print the matrix $1
     puts	    # Draw a line of text, then move to the start of the next line
     qminv       # Quick matrix inversion, done in place
     repeated    # Return an array that is true for repeated elements in $1
     reverse     # reverse the order of a vector
     save_vec    # put the definition of a vector onto the history list
     set_window  # cycle through all available windows
     shade_box   # shade a box, spacing $1, defined by two corners
    shed        # shade region between x y and x2 y2 with n lines
     simp        # Simpson's rule integration: simp answer x y
     smooth      # boxcar smooth a vector
     smooth2     # smooth a vector with a given filter
     smooth3     # smooth a vector with a given filter omitting points
     2dhistogram # convert the current image to a 2-d histogram
      thin        # create a "thinned" version of a vector for plotting points
     thin2       # "thin" the end of a vector (i.e. sparse sampling)
     thin3       # "thin" the central portion of a vector (i.e. sparse sampling)
    uniq        # Remove duplicate elements of $1, e.g. u = uniq(x)
     upper       # define a variable giving an `upper limit' symbol
     vecminmax   # find the minimum and maximum of a vector
     vfield      # plot a vector field: vfield x y len angle
     vfield2      # plot a vector field, vfield x y len angle (len in x-axis units)
     vfield3      # plot a vector field: vfield x y len angle (len in y-axis units)
     vfield4      # Vector field at points x, y; components vx vy
    

    Tips for Mongo Users

    Differences from Mongo

    SM differs in a number of ways from Mongo, and these fall into three groups: those which are enhancements, those which are generalisations, and those which are simply incompatibilities. We do not feel that there is a fourth group for degradations. For those users of Mongo intimidated by change, we note that in most cases it is possible to ignore the enhancements by using a macro presented in the next section. This macro redefines commands to reproduce the old syntax; for example limits is defined to mean LIMITS x y. It is also possible to read Mongo files using the READ OLD command, and the macros input and read_old based upon it. The following list of enhancements in not complete; See the distribution notes from the current release of SM.

    Enhancements:

      Any number of vectors may be defined.
      Vectors may be manipulated arithmetically.
      Vectors are named.
      Vectors may be defined from the keyboard using DO loops or expressions.
      Vectors may be defined using the cursor.
      Any vector may be used for plotting.
      Any vector may be used for the PTYPE or ERRORBAR commands.
      A history feature is implemented.
      The playback buffer may be edited.
      Macros may be defined from the keyboard, and edited.
      A DO construct is available.
      A FOREACH construct is available.
      Character strings may be read from a file and used freely as labels or names.
      Data may be read from rows as well as columns in files.
      Only those parts of a vector satisfying a logical condition need be plotted.
      Vectors may be sorted or fit with splines.
      Macros exist for doing least square fit to sets of points,constructing
      cumulative distributions and histograms, drawing circles, and shading regions.
      All devices have the same range of device coordinates, 0-32767.
      The entire SM environment may be saved for later resumption with SAVE
      and RESTORE.
      The special variable $date expands to the current date and time.
      You can define private point types.
    

    There are also a few incompatibilities:

      DEFINE is used to define variables; macros are defined
      using MACRO.
      Macro arguments must be declared, and are referred to as $n,
      not &n.
      The form LIMITS is not supported (it's meaningless); use
      LIMITS x y, or the macro lim, mentioned above.
      (But note that READ OLD allows for these, and makes suitable changes.)
      WINDOW now takes 4 arguments.
    

    The READ OLD command

    READ OLD reads a Mongo file, converts its contents to a form acceptable to SM, and defines them as a macro. Any macro definition (i.e. from a line beginning def to a line beginning end) is converted to the SM form (i.e. `$s' not `&s') and defined. The commands CONNECT, HISTOGRAM, LIMITS, and POINTS are converted to LIMITS x y, and so forth. ERRORBAR, ECOLUMN, and WINDOWS are also converted. READ OLD will fail if the Mongo file contains abbreviations such as xc for XCOLUMN, then your only hope is to define the same abbreviations. In many cases this will have already been done, for instance xc expands to read x . Comments (beginning !) are optionally converted to standard SM # comments (depending on how the file `read_old.c' was compiled.)

    Note that it is advisable to convert these old Mongo macro files to SM macros, to enable you to take advantage of SM's features. You can do this by simply using READ OLD to read them into SM, and then MACRO WRITE or SAVE to write the converted macro out to disk.

    There is also a macro equivalent of the old INPUT command.

    input     1     ## read and execute a Mongo (not SM) file
                    READ OLD _temp $1
                    _temp
                    MACRO _temp DELETE
    

    The compatibility macro

    This version of compatibility is more complete than in pre-version 2 SM, it also conflicts more strongly with normal SM operations.

    The macro compatibility defines mimics for the Mongo commands which assume that the only vectors are x and y. We strongly recommend that you do not use this macro! If you want to use it anyway, commands like limits alpha beta will give syntax errors. You can turn compatibility mode off again with compatibility 0. The macro itself is a little complicated, it turns off the special meaning of (e.g.) limits, and replaces it with a macro that reproduces the old behaviour, in this case LIMITS x y. The new definitions are in the file `compatible' in the default macro directory, as specified in your `.sm' file. At the time of writing, the commands connect, errorbar, histogram, limits, list, points, read, and window are redefined to reproduce the old syntax. In addition, help is defined to not appear on your history buffer, and define is defined to create macros interactively. You might also be interested in other redefinitions of commands (e.g. list to mean list the playback buffer), if so look at `overloading' in the index. It should be clear that this set of definitions could thoroughly confuse SM if you try to take advantage of its features; in the realm of compatibility mode, it is strictly caveat emptor.

    compatible 11   ## define macros to be compatible with Mongo
                    # If the argument is non-zero or omitted, 
                    # compatibility mode is turned on.
                    # note that some of these make it hard to use regular SM!
                    if($?1 == 0) {
                       compatible 1
                       RETURN
                    }
                    if($1 == 0) { 
                       MACRO DELETE "$!macro"compatible
                    }
                    FOREACH w { connect define errorbar help histogram limits \
                           list points read window write } {
                       OVERLOAD $w $1
                    }
                    if($1) {
                       MACRO READ "$!macro"compatible
                    }
                    #               So newline will end IF statement
    

    SM's Fonts

    These are a sub-set of the well-known Hershey fonts(33) and the available characters are listed in the following table, which were generated from within SM by saying load fonts fonts.

    For details on SM's implementation of TeX and the `traditional' style see the section in the main body of this manual (see section Drawing Labels and SM's TeX Emulation).

    The characters in a font are specified using a programme read_fonts which you can use to make binary font files from the list of Hershey characters, using an index file to specify what character should go where. The binary fonts file also specifies which TeX `definitions' are available (e.g. \alpha). The first 4 bytes of the file are an integer (in binary) that specifies the format of the file; when the format is changed in an incompatible way this number if changed and you will have to rebuild your font files (`make fonts').

    The default font table is illustrated at the end of this appendix. Which font file you want to use is specified as the fonts entry in your `.sm' file.(34). The fonts.bin fonts have been cleaned up a bit for version 2.0 of SM, although the order of characters in the greek and roman fonts is unchanged. There is a new font, `Old English' or \o or \oe, and a good number of new characters are provided. Neither of these fonts supports the `private' fonts, that is there in case users desperately need something, when they can make their own binary font file. For example, there is a set of Hershey oriental fonts that can be used to help SM write Japanese (see section Using SM to write Japanese).

    The complete list of (occidental) Hershey characters is given in a file called hershey_oc.dat, and is in the public domain. Each character is specified by a number in the first 5 columns, then a number of strokes in the next 3, then pairs of letters in the remaining columns up to 72, and in as many 72 character lines as are needed. (Annoyingly, if a line consists of exactly 72 characters, the next must be left blank). Each pair of characters consists of a coordinate, with the origin at (R,R), and the y axis pointing down. A ` ' indicates that the next point is a move, otherwise just connect the dots. The very first pair is different, as it specifies the left and right spacing for the character. If this isn't clear, try drawing a few characters on graph paper, character 2001 (roman A) for example. There are a few characters that have traditionally been available in Mongo that are not in the Hershey set, these have been added to the end of the `hershey_oc.dat' file, plus a few that we thought deserved adding.

    If you want to create your own characters, the macro make_char in `fonts' (i.e. load fonts make_char) might help. It uses the cursor to make a string that is (nearly) in the correct form for inclusion in `hershey_oc.dat'

    The programme read_fonts reads this file, an `index' file that specifies the characters to be put into the fonts, and a list of TeX definitions. The index file consists of character numbers, or ranges consisting of two numbers separated by a minus sign. Comments go from the character # to end of line. Each font consists of 96 characters in ascii order, and fonts appear in the index in the order rm, gr, sc, ti, oe, and then the various private fonts (privateA, privateB, ..., privateG.). You can also use cat_fonts to concatenate binary font files prepared with read_fonts.

    The format of the TeX definition file is that each definition has a line to itself, lines starting with a # are comments. A line consists of a name, some whitespace, the number of arguments (optional, defaults to zero), the name of the font to use, a single white-space character, and the value of the definition to the end of the line; you can continue onto another line by putting a \ at the end of the line. You can use any of the normal font specifications, or cu which means use the current font.

    For example

    alpha        gr a
    alsoalpha  0 cu \gr a
    alphatoo   1 gr \#1a
    

    defines \alpha the conventional way as the character a in the greek font, then defines alsoalpha in a less efficient way (by specifying the current font, then explicitly switching to greek), then defines alphatoo as a large , used as \alphatoo5. There's no reason why your definitions can't be reasonably complicated, see for example the definition of \TeX.

    If the number of arguments is given as -1 the TeX symbol will be interpreted as an alias for the specified font, for example the TeX definition file begins

    katakana -1 privateA
    hiragana -1 privateB
    

    to make \katakana change to the privateA font.

    The main Makefile prepares your binary font file for you. There is also a programme cat_fonts that can be used to concatenate two or more binary fonts files; an example is given in the section on Japanese fonts (section Using SM to write Japanese).

    It is also possible to build SM fonts for other languages:

    Using SM to write Japanese

    SM is currently able to use either katakana or hiragana fonts, but no kanji are yet supported. The order of characters in the fonts is likely to change, based on the desires of the users. Please let me know what you want.

    As distributed, the Japanese font tables aren't built. To make them, say make jfonts.bin in the toplevel SM directory, and ensure that your `.sm' file sets fonts correctly. The Japanese characters are available as \hiragana and \katakana. You can list the characters available with load fonts jTeX_fonts.

    One question is whether \katakana 2 should print an Arabic (western) or a Japanese two; I am open to suggestions.

    There are a variety of kanji characters present, but no easy way to select them. If someone would like to donate a table of the correspondence between some accepted ascii encoding and the available characters, I will ensure that the character encoded as AB os available as (say) \kanjiAB, and provide a programme to generate such codes from the output of at least one popular word processor, expecting the user to cut-and-paste the result. Of course, it'd be quicker if you did it for me!


    Go to the first, previous, next, last section, table of contents.