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 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 (The grammar is actually specified using YACC, see S.C. Johnson YACC: Yet Another Compiler Compiler, Computing Science Technical Report No. 32, 1975, AT&T Bell laboratories, Murray Hill, NJ07974. This report is reprinted in section 2b of the UNIX manual, and is rather difficult reading at first. We do not in fact use the AT&T code, which is proprietary, but rather a public domain compiler-compiler called Bison written by the Free Software Foundation.).

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. (Specifying -s on the command line bypasses all of this, and makes SM read input one line at a time.) 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 (i.e. tokens that have been recognised, typically keywords) 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 BB(1-sqrt(2)/2) 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.

     [file stdgraph.txi not included]

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). (Under Unix the loader can often distinguish fortran from C, so you may not need the `f' -- call sm_limits(0.0,1.0,0.0,1.0)) If your fortran compiler cannot find the function names, you might have to read the section on how SM chooses function names (see Fortran Names).

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 Why call sm_alpha?.

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_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_connect(x,y,n)
     Connect a line. (real x(n),y(n); integer n)

sm_conn(x,y,n)
     Equivalent to sm_connect

sm_ctype(c)
     Set colour to c (character c)

sm_ctype_i(i)
     Set colour to i (integer i)

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_page()
     Equivalent to PAGE interactively

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_set_ctype(cols,n)
     Set SM's permitted colours to cols (real cols; integer n)

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 colour functions are sm_ctype and sm_ctype_i, equivalent to CTYPE name and CTYPE integer respectively, and sm_set_ctype, equivalent to the numerical form of CTYPE = expr.

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:

     -------------------------------------------------------
     |      |      |     :    :     |     :    :     |
     |  FS  |  HS  |  RS : RL : RE  |  RS : RL : RE  | ...
     |      |      |     :    :     |     :    :     |
     -------------------------------------------------------

The distinction between solid and dotted lines is purely visual. 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:

     --------------------------------------------------
     |      |     :      :     |     :    :     |
     |  FS  |  RS : RL_H : RE  |  RS : RL : RE  | ...
     |      |     :      :     |     :    :     |
     --------------------------------------------------

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 (in fact they will be read from a macro or the command line without prompting if they are available; try define file_type no_header image file \n 10 20) 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 (The usual reference is Wells, D.C., et al. Astron. Astroph. Suppl, 44 363 (1981), although FITS is in fact becoming a national standard in the US). 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 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 Graphcap), 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 sm.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 macrolist.txi not included]

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

     [file fonts.txi not included]