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