1 /*
2 Copyright (c) 1991-2002, The Numerical ALgorithms Group Ltd.
3 All rights reserved.
4 
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are
7 met:
8 
9     - Redistributions of source code must retain the above copyright
10       notice, this list of conditions and the following disclaimer.
11 
12     - Redistributions in binary form must reproduce the above copyright
13       notice, this list of conditions and the following disclaimer in
14       the documentation and/or other materials provided with the
15       distribution.
16 
17     - Neither the name of The Numerical ALgorithms Group Ltd. nor the
18       names of its contributors may be used to endorse or promote products
19       derived from this software without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
25 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33 
34 /*
35  * Lexical analyzer stuff. Exported functions:
36  *
37  * -- parser_init()
38  * initialize the parser tables with keywords
39  *
40  * -- init_scanner()
41  * initialize scanner for reading a new page
42  *
43  * -- get_token()
44  * sets the "token" variable to be the next token in the current input stream
45  *
46  * -- save_scanner_state()
47  * save the current state of scanner so that the scanner input mode may
48  * be switched
49  *
50  * -- restore_scanner_state()
51  * undo the saved state
52  *
53  * Note: The scanner reads from three separate input locations depending on the
54  * value of the variable "input_type".  If this variable is:
55  *
56  * FromFile       -- it read from the file pointed to by "cfile".
57  * FromString     -- It reads from the string "input_string".
58  * FromSpadSocket -- It reads from the socket pointed to by spad_socket
59  * FromFD         -- It reads from a file descriptor
60  *
61  * New variable useAscii -- tells us if we we should translate
62  * graphics characters on the fly
63  * initialised in init_scanner
64  *
65  */
66 
67 #include "fricas_c_macros.h"
68 #include "debug.h"
69 
70 int useAscii;
71 
72 #define PARSER 1
73 
74 #include "hyper.h"
75 #include "hterror.h"
76 #include "lex.h"
77 
78 #include "all_hyper_proto.H1"
79 #include "sockio-c.H1"
80 
81 
82 #include <ctype.h>
83 #include <setjmp.h>
84 
85 static int get_char1(void);
86 static void spad_error_handler(void);
87 static int keyword_type(void);
88 
89 extern int gTtFontIs850;
90 
91 
92 StateNode *top_state_node;
93 HyperDocPage *gPageBeingParsed;      /* page currently being parsed    */
94 extern jmp_buf jmpbuf;
95 extern char ebuffer[];
96 short int gInSpadsrc = 0;
97 short int gInVerbatim;
98 
99 /* Parser variables */
100 long fpos;                      /* Position of pointer in file in characters */
101 long page_start_fpos;           /* where the current pages fpos started      */
102 long keyword_fpos;              /* fpos of beginning of most recent keyword */
103 Token token;                    /* most recently read token */
104 int last_token;                 /* most recently read token for unget_token */
105 int input_type;                 /* indicates where to read input */
106 char *input_string;             /* input string read when from_string is true */
107 int last_ch;                    /* last character read, for unget_char */
108 int last_command;               /* the last socket command */
109 int keyword;                    /* the last command was a keyword, or a group */
110 int cfd;                        /* current file descriptor */
111 FILE *cfile;                    /* currently active file pointer */
112 FILE *unixfd;
113 int line_number;
114 
115 char sock_buf[1024];            /* buffer for socket input */
116 
117 #define TokenHashSize   100
118 
119 static HashTable tokenHashTable;           /* hash table of parser tokens */
120 
121 void
dumpToken(char * caller,Token t)122 dumpToken(char *caller, Token t)
123 { fprintf(stderr,"%s:dumpToken type=%s id=%s\n",
124     caller,token_table[t.type],t.id);
125 }
126 
127 
128 /* initialize the parser keyword hash table */
129 void
parser_init(void)130 parser_init(void)
131 {
132     int i;
133     Token *toke;
134 
135     /* First I initialize the hash table for the tokens */
136 
137     hash_init(
138               &tokenHashTable,
139               TokenHashSize,
140               (EqualFunction)string_equal,
141               (HashcodeFunction)string_hash);
142     for (i = 2; i <= NumberUserTokens; i++) {
143         toke = (Token *) halloc(sizeof(Token), "Token");
144         toke->type = i;
145         toke->id = token_table[i];
146         hash_insert(&tokenHashTable, (char *)toke, toke->id);
147     }
148 
149 }
150 
151 /* initialize the lexical scanner to read from a file */
152 void
init_scanner(void)153 init_scanner(void)
154 {
155     if (getenv("HTASCII")) {
156         useAscii = (strcmp(getenv("HTASCII"), "yes") == 0);
157     }
158     else {
159         if(gTtFontIs850==1) useAscii = 0;
160         else useAscii = 1;
161     }
162     keyword = 0;
163     last_ch = NoChar;
164     last_token = 0;
165     input_type = FromFile;
166     fpos = 0;
167     keyword_fpos = 0;
168     last_command = -1;
169     line_number = 1;
170 }
171 
172 /*
173  * variables to save current state of scanner.  Currently only one level of
174  * saving is allowed.  In the future we should allow nested saves
175  */
176 
177 /* save the current state of the scanner */
178 void
save_scanner_state(void)179 save_scanner_state(void)
180 {
181     StateNode *new_item = (StateNode *) halloc((sizeof(StateNode)), "StateNode");
182 
183     new_item->page_start_fpos = page_start_fpos;
184     new_item->fpos = fpos;
185     new_item->keyword_fpos = keyword_fpos;
186     new_item->last_ch = last_ch;
187     new_item->last_token = last_token;
188     new_item->token = token;
189     new_item->input_type = input_type;
190     new_item->input_string = input_string;
191     new_item->cfile = cfile;
192     new_item->next = top_state_node;
193     new_item->keyword = keyword;
194     top_state_node = new_item;
195 }
196 
197 /* restore the saved scanner state */
198 void
restore_scanner_state(void)199 restore_scanner_state(void)
200 {
201     StateNode *x = top_state_node;
202 
203     if (top_state_node == NULL) {
204         fprintf(stderr, "Restore Scanner State: State empty\n");
205         exit(-1);
206     }
207     top_state_node = top_state_node->next;
208     page_start_fpos = x->page_start_fpos;
209     fpos = x->fpos;
210     keyword_fpos = x->keyword_fpos;
211     last_ch = x->last_ch;
212     last_token = x->last_token;
213     token = x->token;
214     input_type = x->input_type;
215     input_string = x->input_string;
216     cfile = x->cfile;
217     keyword = x->keyword;
218     if (cfile != NULL)
219         fseek(cfile, fpos + page_start_fpos, 0);
220     /** Once that is done, lets throw away some memory **/
221     free(x);
222 }
223 
224 /* return the character to the input stream. */
225 void
unget_char(int c)226 unget_char(int c)
227 {
228     if (c == '\n')
229         line_number--;
230     last_ch = c;
231 }
232 
233 int
get_char(void)234 get_char(void)
235 {
236     int c;
237 
238     c = get_char1();
239     if (useAscii) {
240         switch (c) {
241           case '�':
242             c = '-';
243             break;
244           case '�':
245             c = '+';
246             break;
247           case '�':
248             c = '[';
249             break;
250           case '�':
251             c = '+';
252             break;
253           case '�':
254             c = '-';
255             break;
256           case '�':
257             c = '+';
258             break;
259           case '�':
260             c = '-';
261             break;
262           case '�':
263             c = '+';
264             break;
265           case '�':
266             c = ']';
267             break;
268           case '�':
269             c = '+';
270             break;
271           case '�':
272             c = '|';
273             break;
274           default:
275             break;
276         }
277     }
278     return c;
279 }
280 
281 char * read_again = 0;
282 
283 /* return the next character in the input stream */
284 static int
get_char1(void)285 get_char1(void)
286 {
287     int c;
288     int cmd;
289 
290     if (last_ch != NoChar) {
291         c = last_ch;
292         last_ch = NoChar;
293         if (c == '\n')
294             line_number++;
295         return c;
296     }
297     switch (input_type) {
298       case FromUnixFD:
299         c = getc(unixfd);
300         if (c == '\n')
301             line_number++;
302         return c;
303       case FromString:
304         c = (*input_string ? *input_string++ : EOF);
305         if (c == '\n')
306             line_number++;
307         return c;
308       case FromFile:
309         c = getc(cfile);
310         fpos++;
311         if (c == '\n')
312             line_number++;
313         return c;
314       case FromSpadSocket:
315 AGAIN:
316         if (*input_string) {
317             /* this should never happen for the first character */
318             c = *input_string++;
319             if (c == '\n')
320                 line_number++;
321             return c;
322         }
323         if (last_command == EndOfPage)
324             return EOF;
325         if (read_again == NULL) {
326             last_command = cmd = get_int(spad_socket);
327             if (cmd == EndOfPage)
328                 return EOF;
329             if (cmd == SpadError)
330                 spad_error_handler();
331         }
332         read_again = get_string_buf(spad_socket, sock_buf, 1023);
333         /* this will be null if this is the last time*/
334         input_string = sock_buf;
335         goto AGAIN;
336       default:
337         fprintf(stderr, "Get Char: Unknown type of input: %d\n", input_type);
338         return -1;
339     }
340 }
341 
342 
343 #define special(c) ((c) == '{' || (c) == '}' || (c) == '#' || (c) == '%' || \
344                     (c) == '\\'  || (c) == '[' || (c) == ']' || (c) == '_' || \
345                     (c) == ' ' || (c) == '$' || (c) == '~' || (c) == '^' ||  \
346                     (c) == '&')
347 
348 #define punctuation(c) ((c)== '`' || (c) == '\'' || (c) == ','  || \
349                         (c) == '.' || (c) == '?' || (c) == '"' || \
350                         (c)  == ';' || (c) == ':' || (c) == '-')
351 
352 #define whitespace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
353 #define delim(c) \
354   (whitespace(c) || special(c) || punctuation(c))
355 
356 
357 
358 Token unget_toke;
359 
360 /* return current token to the input stream */
361 void
unget_token(void)362 unget_token(void)
363 {
364     last_token = 1;
365     unget_toke.type = token.type;
366     unget_toke.id = alloc_string(token.id - 1);
367 }
368 
369 
370 int
get_token(void)371 get_token(void)
372 {
373     int c, ws;
374     int nls = 0;
375     static int seen_white = 0;
376     static char buffer[1024];
377     char *buf = buffer;
378 
379     if (last_token) {
380         last_token = 0;
381         token.type = unget_toke.type;
382         strcpy(buffer, unget_toke.id);
383         free(unget_toke.id);
384         token.id = buffer + 1;
385         if (token.type == EOF)
386             return EOF;
387         else
388             return 0;
389     }
390     seen_white = nls = 0;
391     do {
392         c = get_char();
393         ws = whitespace(c);
394         if (ws)
395             seen_white++;
396         if (c == '\n') {
397             if (nls) {
398                 token.type = Par;
399                 return 0;
400             }
401             else
402                 nls++;
403         }
404     } while (ws);
405 
406     /* first character of string indicates number of spaces before token */
407 
408     if (!keyword)
409         *buf++ = seen_white;
410     else
411         *buf++ = 0;
412 
413     keyword = 0;
414     if (input_type != FromSpadSocket && c == '%') {
415         while ((c = get_char()) != '\n' && c != EOF);
416 /* trying to fix the comment problem: a comment line forces words on either side together*/
417 /* try returning the eol */
418         unget_char(c);
419         return get_token();
420     }
421     if (input_type == FromFile && c == '$') {
422         token.type = Dollar;
423         return 0;
424     }
425     switch (c) {
426       case EOF:
427         token.type = -1;
428         return EOF;
429       case '\\':
430         keyword_fpos = fpos - 1;
431         c = get_char();
432         if (!isalpha(c)) {
433             *buf++ = c;
434             token.type = Word;
435             *buf = '\0';
436             seen_white = 0;
437         }
438         else {
439             do {
440                 *buf++ = c;
441             } while ((c = get_char()) != EOF && isalpha(c));
442 
443             unget_char(c);
444             *buf = '\0';
445             keyword = 1;
446             token.id = buffer + 1;
447             return (keyword_type());
448         }
449         break;
450       case '{':
451         token.type = Lbrace;
452         break;
453       case '}':
454         token.type = Rbrace;
455         break;
456       case '[':
457         token.type = Lsquarebrace;
458         *buf++ = c;
459         *buf = '\0';
460         token.id = buffer + 1;
461         break;
462       case ']':
463         token.type = Rsquarebrace;
464         *buf++ = c;
465         *buf = '\0';
466         token.id = buffer + 1;
467         break;
468       case '#':
469         token.type = Pound;
470 
471         /*
472          * if I get a pound then what I do is parse until I get something
473          * that is not an integer
474          */
475         c = get_char();
476         while (isdigit(c) && (c != EOF)) {
477             *buf++ = c;
478             c = get_char();
479         }
480         unget_char(c);
481         *buf = '\0';
482         token.id = buffer + 1;
483         break;
484       case '`':
485       case '\'':
486       case ',':
487       case '.':
488       case '!':
489       case '?':
490       case '"':
491       case ':':
492       case ';':
493         token.type = Punctuation;
494         *buf++ = c;
495         *buf = '\0';
496         /** Now I should set the buffer[0] as my flag for whether I had
497           white-space in front of me, and whether I had white space
498           behind me **/
499         if (buffer[0])
500             buffer[0] = FRONTSPACE;
501         c = get_char();
502         if (whitespace(c))
503             buffer[0] |= BACKSPACE;
504         unget_char(c);
505         token.id = buffer + 1;
506         break;
507       case '-':
508         do {
509             *buf++ = c;
510         } while (((c = get_char()) != EOF) && (c == '-'));
511         unget_char(c);
512         *buf = '\0';
513         token.type = Dash;
514         token.id = buffer + 1;
515         break;
516       default:
517         do {
518             *buf++ = c;
519         } while ((c = get_char()) != EOF && !delim(c));
520         unget_char(c);
521         *buf = '\0';
522         token.type = Word;
523         token.id = buffer + 1;
524         break;
525     }
526 /*    dumpToken("get_token",token);*/
527     return 0;
528 }
529 
530 
531 /*
532  * Here are the structures and stuff needed for the begin and end routines.
533  * The stack stores the begin types that have been seen and the end
534  * pops them off and checks to insure that they are reversed properly.
535  */
536 
537 typedef struct be_struct {
538     int type;
539     char *id;
540     struct be_struct *next;
541 }   BeStruct;
542 
543 BeStruct *top_be_stack;
544 
545 
546 void
push_be_stack(int type,char * id)547 push_be_stack(int type,char * id)
548 {
549     BeStruct *be = (BeStruct *) halloc(sizeof(BeStruct), "BeginENd stack");
550 
551     if (gWindow != NULL) {
552         be->type = type;
553         be->next = top_be_stack;
554         be->id = alloc_string(id);
555         top_be_stack = be;
556     }
557     return;
558 }
559 void
check_and_pop_be_stack(int type,char * id)560 check_and_pop_be_stack(int type,char * id)
561 {
562     BeStruct *x;
563 
564     /*
565      * this routine pops the be stack and compares types. If they are
566      * the same then I am okay and return a 1. Else I return a two and try to
567      * print a meaningful message
568      */
569     if (gWindow == NULL)
570         return;
571     if (top_be_stack == NULL) { /* tried to pop when I shouldn't have */
572         fprintf(stderr, "Unexpected \\end{%s} \n", token.id);
573         print_page_and_filename();
574         print_next_ten_tokens();
575         jump();
576     }
577     x = top_be_stack;
578     if (x->type == type) {
579         top_be_stack = top_be_stack->next;
580         free(x->id);
581         free(x);
582         return;
583     }
584     /* else I didn't have a match. Lets try to write a sensible message */
585     fprintf(stderr, "\\begin{%s} ended with \\end{%s} \n", x->id, id);
586     print_page_and_filename();
587     print_next_ten_tokens();
588     jump();
589 }
590 
591 int
clear_be_stack(void)592 clear_be_stack(void)
593 {
594     BeStruct *x = top_be_stack, *y;
595 
596     top_be_stack = NULL;
597     while (x != NULL) {
598         y = x->next;
599         free(x);
600         x = y;
601     }
602     return 1;
603 }
604 
605 int
be_type(char * which)606 be_type(char *which)
607 {
608     Token store;
609 
610     get_expected_token(Lbrace);
611     get_expected_token(Word);
612     switch (token.id[0]) {
613       case 't':
614         if (!strcmp(token.id, "titems")) {
615             token.type = Begintitems;
616         }
617         else {
618             return -1;
619         }
620         break;
621       case 'p':
622         if (!strcmp(token.id, "page")) {
623             token.type = Page;
624         }
625         else if (!strcmp(token.id, "paste")) {
626             token.type = Paste;
627         }
628         else if (!strcmp(token.id, "patch")) {
629             token.type = Patch;
630         }
631         else {
632             return -1;
633         }
634         break;
635       case 'v':         /* possibly a verbatim mode */
636         if (!strcmp(token.id, "verbatim")) {
637             token.type = Verbatim;
638         }
639         else {
640             return -1;
641         }
642         break;
643       case 's':         /* possibly a scroll mode */
644         if (!strcmp("scroll", token.id)) {
645             token.type = Beginscroll;
646         }
647         else if (!strcmp(token.id, "spadsrc")) {
648             token.type = Spadsrc;
649         }
650         else {
651             return -1;
652         }
653         break;
654       case 'i':         /* possibly a item */
655         if (!strcmp("items", token.id)) {
656             token.type = Beginitems;
657         }
658         else {
659             return -1;
660         }
661         break;
662       default:
663         return -1;
664     }
665     store.type = token.type;
666     /* store.id = alloc_string(token.id); */
667     get_expected_token(Rbrace);
668     token.type = store.type;
669 
670     /*
671      * strcpy(token.id, store.id); free(store.id);
672      */
673     return 0;
674 
675 }
676 int
begin_type(void)677 begin_type(void)
678 {
679     /*Token store;*/
680     int ret_val;
681 
682     /*
683      * This routine parses a statement of the form \begin{word}. Once it has
684      * read the word it tries to assign it a type. Once that is done it sends
685      * the word id, and the type to push_be_stack and then returns the type.
686      * For the moment I am not even going to use a has_table, although in the
687      * future this may be needed
688      */
689     ret_val = be_type("begin");
690     if (ret_val == -1) {
691         if (gWindow == NULL || gInVerbatim)
692             return 1;
693         else {
694             fprintf(stderr, "Unknown begin type \\begin{%s} \n", token.id);
695             print_page_and_filename();
696             print_next_ten_tokens();
697             jump();
698         }
699     }
700     else {
701         if (gWindow != NULL && !gInVerbatim && token.type != Verbatim
702             && token.type != Spadsrc) {
703             /* Now here I should push the needed info and then get */
704             push_be_stack(token.type, token.id);
705         }
706         return 1;
707     }
708     return 1;
709 }
710 
711 
712 int
end_type(void)713 end_type(void)
714 {
715     int ret;
716 
717     /*
718      * This routine gets the end type just as the begin_type routine does,
719      * But then it checks to see if received the proper end_type. By a clever
720      * trick, the proper end type is 3000 + type. When environments this will
721      * have to change
722      */
723     ret = be_type("end");
724     if (ret == -1) {
725         /* unrecognized end token */
726         if (gWindow == NULL || gInVerbatim) {
727             return 1;
728         }
729         else {
730             fprintf(stderr, "Unknown begin type \\begin{%s} \n", token.id);
731             print_page_and_filename();
732             print_next_ten_tokens();
733             jump();
734         }
735     }
736     else {
737         if (gWindow != NULL && !gInVerbatim) {
738             check_and_pop_be_stack(token.type, token.id);
739             token.type += 3000;
740             return 1;
741         }
742         else {
743             if (gWindow != NULL && ((gInVerbatim && token.type == Verbatim) ||
744                                 (gInSpadsrc && token.type == Spadsrc))) {
745                 check_and_pop_be_stack(token.type, token.id);
746                 token.type += 3000;
747                 return 1;
748             }
749             else {
750                 token.type += 3000;
751                 return 1;
752             }
753         }
754     }
755     return 1;
756 }
757 
758 
759 
760 static int
keyword_type(void)761 keyword_type(void)
762 {
763     Token *token_ent;
764 
765     /* first check to see if it is a reserved token */
766     token_ent = (Token *) hash_find(&tokenHashTable, token.id);
767     if (token_ent != NULL) {
768         token.type = token_ent->type;
769 
770         /*
771          * if I am a keyword I also have to check to see if I am a begin or
772          * an end
773          */
774         if (token.type == Begin)
775             return begin_type();
776         if (token.type == End)
777             return end_type();
778         /* next check to see if it is a macro */
779     }
780     else if (gWindow != NULL) {
781         if (hash_find(gWindow->fMacroHashTable, token.id) != NULL)
782             token.type = Macro;
783         else if (gPageBeingParsed->box_hash != NULL &&
784                  hash_find(gPageBeingParsed->box_hash, token.id) != NULL)
785         {
786             token.type = Boxcond;
787         }
788         else if (hash_find(gWindow->fCondHashTable, token.id) != NULL)
789             token.type = Cond;
790         else                    /* We have no idea what we've got */
791             token.type = Unkeyword;
792     }
793     else {                      /* We are probably in htadd so just return. It
794                                  * is only concerned with pages anyway */
795         token.type = Unkeyword;
796     }
797     return 0;
798 }
799 
800 /* read a token, and report a syntax error if it has the wrong type */
801 void
get_expected_token(int type)802 get_expected_token(int type)
803 {
804     get_token();
805     if (token.type != type) {
806         token_name(type);
807         fprintf(stderr, "syntax error: expected a %s\n", ebuffer);
808         if (token.type == EOF) {
809             print_page_and_filename();
810             fprintf(stderr, "Unexpected EOF\n");
811         }
812         else {
813             token_name(token.type);
814             fprintf(stderr, "not a %s\n", ebuffer);
815             print_page_and_filename();
816             print_next_ten_tokens();
817         }
818         longjmp(jmpbuf, 1);
819         fprintf(stderr, "Could not jump to Error Page\n");
820         exit(-1);
821     }
822 }
823 
824 
825 static void
spad_error_handler(void)826 spad_error_handler(void)
827 {
828     /* fprintf(stderr, "got a spad error\n"); */
829     longjmp(jmpbuf, 1);
830     fprintf(stderr, "(HyperDoc) Fatal Error: Could not jump to Error Page.\n");
831     exit(-1);
832 }
833 
834 extern int still_reading, str_len;
835 void
reset_connection(void)836 reset_connection(void)
837 {
838     if (spad_socket) {
839         FD_CLR(spad_socket->socket, &socket_mask);
840         purpose_table[spad_socket->purpose] = NULL;
841         close(spad_socket->socket);
842         spad_socket->socket = 0;
843         spad_socket = NULL;
844         if (input_string)
845             input_string = "";
846         read_again = 0;
847         str_len = 0;
848         still_reading = 0;
849         connect_spad();
850     }
851 }
852 
853 
854 /* returns true if spad is currently computing */
855 int
spad_busy(void)856 spad_busy(void)
857 {
858     if (session_server == NULL)
859         return 1;
860     send_int(session_server, QuerySpad);
861     return get_int(session_server);
862 }
863 
864 /* connect to FRICAS, return 0 if successful, 1 if not */
865 int
connect_spad(void)866 connect_spad(void)
867 {
868     if (!MenuServerOpened) {
869         fprintf(stderr, "(HyperDoc) Warning: Not connected to FriCAS Server!\n");
870         LoudBeepAtTheUser();
871         return NotConnected;
872     }
873     if (spad_socket == NULL) {
874         spad_socket = connect_to_local_server(SpadServer, MenuServer, Forever);
875         if (spad_socket == NULL) {
876             fprintf(stderr, "(HyperDoc) Warning: Could not connect to FriCAS Server!\n");
877             LoudBeepAtTheUser();
878             return NotConnected;
879         }
880     }
881     /* if (spad_busy()) return SpadBusy; */
882     return Connected;
883 }
884