xref: /PHP-5.5/ext/xmlrpc/libxmlrpc/queue.c (revision 2154e7b5)
1 static const char rcsid[] = "#(@) $Id$";
2 
3 /*
4  * Date last modified: Jan 2001
5  * Modifications by Dan Libby (dan@libby.com), including:
6  *  - various fixes, null checks, etc
7  *  - addition of Q_Iter funcs, macros
8  */
9 
10 
11 /*-**************************************************************
12  *
13  *  File : q.c
14  *
15  *  Author: Peter Yard [1993.01.02] -- 02 Jan 1993
16  *
17  *  Disclaimer: This code is released to the public domain.
18  *
19  *  Description:
20  *      Generic double ended queue (Deque pronounced DEK) for handling
21  *      any data types, with sorting.
22  *
23  *      By use of various functions in this module the caller
24  *      can create stacks, queues, lists, doubly linked lists,
25  *      sorted lists, indexed lists.  All lists are dynamic.
26  *
27  *      It is the responsibility of the caller to malloc and free
28  *      memory for insertion into the queue. A pointer to the object
29  *      is used so that not only can any data be used but various kinds
30  *      of data can be pushed on the same queue if one so wished e.g.
31  *      various length string literals mixed with pointers to structures
32  *      or integers etc.
33  *
34  *  Enhancements:
35  *      A future improvement would be the option of multiple "cursors"
36  *      so that multiple locations could occur in the one queue to allow
37  *      placemarkers and additional flexibility.  Perhaps even use queue
38  *      itself to have a list of cursors.
39  *
40  * Usage:
41  *
42  *          /x init queue x/
43  *          queue  q;
44  *          Q_Init(&q);
45  *
46  *      To create a stack :
47  *
48  *          Q_PushHead(&q, &mydata1); /x push x/
49  *          Q_PushHead(&q, &mydata2);
50  *          .....
51  *          data_ptr = Q_PopHead(&q); /x pop x/
52  *          .....
53  *          data_ptr = Q_Head(&q);   /x top of stack x/
54  *
55  *      To create a FIFO:
56  *
57  *          Q_PushHead(&q, &mydata1);
58  *          .....
59  *          data_ptr = Q_PopTail(&q);
60  *
61  *      To create a double list:
62  *
63  *          data_ptr = Q_Head(&q);
64  *          ....
65  *          data_ptr = Q_Next(&q);
66  *          data_ptr = Q_Tail(&q);
67  *          if (Q_IsEmpty(&q)) ....
68  *          .....
69  *          data_ptr = Q_Previous(&q);
70  *
71  *      To create a sorted list:
72  *
73  *          Q_PushHead(&q, &mydata1); /x push x/
74  *          Q_PushHead(&q, &mydata2);
75  *          .....
76  *          if (!Q_Sort(&q, MyFunction))
77  *              .. error ..
78  *
79  *          /x fill in key field of mydata1.
80  *           * NB: Q_Find does linear search
81  *           x/
82  *
83  *          if (Q_Find(&q, &mydata1, MyFunction))
84  *          {
85  *              /x found it, queue cursor now at correct record x/
86  *              /x can retrieve with x/
87  *              data_ptr = Q_Get(&q);
88  *
89  *              /x alter data , write back with x/
90  *              Q_Put(&q, data_ptr);
91  *          }
92  *
93  *          /x Search with binary search x/
94  *          if (Q_Seek(&q, &mydata, MyFunction))
95  *              /x etc x/
96  *
97  *
98  ****************************************************************/
99 
100 #ifdef _WIN32
101 #include "xmlrpc_win32.h"
102 #endif
103 #include <stdlib.h>
104 #include "queue.h"
105 
106 
107 static void QuickSort(void *list[], int low, int high,
108                       int (*Comp)(const void *, const void *));
109 static int  Q_BSearch(queue *q, void *key,
110                       int (*Comp)(const void *, const void *));
111 
112 /* The index: a pointer to pointers */
113 
114 static  void        **index;
115 static  datanode    **posn_index;
116 
117 
118 /***
119  *
120  ** function    : Q_Init
121  *
122  ** purpose     : Initialise queue object and pointers.
123  *
124  ** parameters  : 'queue' pointer.
125  *
126  ** returns     : True_ if init successful else False_
127  *
128  ** comments    :
129  ***/
130 
Q_Init(queue * q)131 int Q_Init(queue  *q)
132 {
133    if(q) {
134       q->head = q->tail = NULL;
135       q->cursor = q->head;
136       q->size = 0;
137       q->sorted = False_;
138    }
139 
140    return True_;
141 }
142 
143 /***
144  *
145  ** function    : Q_AtHead
146  *
147  ** purpose     : tests if cursor is at head of queue
148  *
149  ** parameters  : 'queue' pointer.
150  *
151  ** returns     : boolean - True_ is at head else False_
152  *
153  ** comments    :
154  *
155  ***/
156 
Q_AtHead(queue * q)157 int Q_AtHead(queue *q)
158 {
159    return(q && q->cursor == q->head);
160 }
161 
162 
163 /***
164  *
165  ** function    : Q_AtTail
166  *
167  ** purpose     : boolean test if cursor at tail of queue
168  *
169  ** parameters  : 'queue' pointer to test.
170  *
171  ** returns     : True_ or False_
172  *
173  ** comments    :
174  *
175  ***/
176 
Q_AtTail(queue * q)177 int Q_AtTail(queue *q)
178 {
179    return(q && q->cursor == q->tail);
180 }
181 
182 
183 /***
184  *
185  ** function    : Q_IsEmpty
186  *
187  ** purpose     : test if queue has nothing in it.
188  *
189  ** parameters  : 'queue' pointer
190  *
191  ** returns     : True_ if IsEmpty queue, else False_
192  *
193  ** comments    :
194  *
195  ***/
196 
Q_IsEmpty(queue * q)197 inline int Q_IsEmpty(queue *q)
198 {
199    return(!q || q->size == 0);
200 }
201 
202 /***
203  *
204  ** function    : Q_Size
205  *
206  ** purpose     : return the number of elements in the queue
207  *
208  ** parameters  : queue pointer
209  *
210  ** returns     : number of elements
211  *
212  ** comments    :
213  *
214  ***/
215 
Q_Size(queue * q)216 int Q_Size(queue *q)
217 {
218    return q ? q->size : 0;
219 }
220 
221 
222 /***
223  *
224  ** function    : Q_Head
225  *
226  ** purpose     : position queue cursor to first element (head) of queue.
227  *
228  ** parameters  : 'queue' pointer
229  *
230  ** returns     : pointer to data at head. If queue is IsEmpty returns NULL
231  *
232  ** comments    :
233  *
234  ***/
235 
Q_Head(queue * q)236 void *Q_Head(queue *q)
237 {
238    if(Q_IsEmpty(q))
239       return NULL;
240 
241    q->cursor = q->head;
242 
243    return  q->cursor->data;
244 }
245 
246 
247 /***
248  *
249  ** function    : Q_Tail
250  *
251  ** purpose     : locate cursor at tail of queue.
252  *
253  ** parameters  : 'queue' pointer
254  *
255  ** returns     : pointer to data at tail , if queue IsEmpty returns NULL
256  *
257  ** comments    :
258  *
259  ***/
260 
Q_Tail(queue * q)261 void *Q_Tail(queue *q)
262 {
263    if(Q_IsEmpty(q))
264       return NULL;
265 
266    q->cursor = q->tail;
267 
268    return  q->cursor->data;
269 }
270 
271 
272 /***
273  *
274  ** function    : Q_PushHead
275  *
276  ** purpose     : put a data pointer at the head of the queue
277  *
278  ** parameters  : 'queue' pointer, void pointer to the data.
279  *
280  ** returns     : True_ if success else False_ if unable to push data.
281  *
282  ** comments    :
283  *
284  ***/
285 
Q_PushHead(queue * q,void * d)286 int Q_PushHead(queue *q, void *d)
287 {
288    if(q && d) {
289       node    *n;
290       datanode *p;
291 
292       p = malloc(sizeof(datanode));
293       if(p == NULL)
294          return False_;
295 
296       n = q->head;
297 
298       q->head = (node*)p;
299       q->head->prev = NULL;
300 
301       if(q->size == 0) {
302          q->head->next = NULL;
303          q->tail = q->head;
304       }
305       else {
306          q->head->next = (datanode*)n;
307          n->prev = q->head;
308       }
309 
310       q->head->data = d;
311       q->size++;
312 
313       q->cursor = q->head;
314 
315       q->sorted = False_;
316 
317       return True_;
318    }
319    return False_;
320 }
321 
322 
323 
324 /***
325  *
326  ** function    : Q_PushTail
327  *
328  ** purpose     : put a data element pointer at the tail of the queue
329  *
330  ** parameters  : queue pointer, pointer to the data
331  *
332  ** returns     : True_ if data pushed, False_ if data not inserted.
333  *
334  ** comments    :
335  *
336  ***/
337 
Q_PushTail(queue * q,void * d)338 int Q_PushTail(queue *q, void *d)
339 {
340    if(q && d) {
341       node        *p;
342       datanode    *n;
343 
344       n = malloc(sizeof(datanode));
345       if(n == NULL)
346          return False_;
347 
348       p = q->tail;
349       q->tail = (node *)n;
350 
351       if(q->size == 0) {
352          q->tail->prev = NULL;
353          q->head = q->tail;
354       }
355       else {
356          q->tail->prev = (datanode *)p;
357          p->next = q->tail;
358       }
359 
360       q->tail->next = NULL;
361 
362       q->tail->data =  d;
363       q->cursor = q->tail;
364       q->size++;
365 
366       q->sorted = False_;
367 
368       return True_;
369    }
370    return False_;
371 }
372 
373 
374 
375 /***
376  *
377  ** function    : Q_PopHead
378  *
379  ** purpose     : remove and return the top element at the head of the
380  *                queue.
381  *
382  ** parameters  : queue pointer
383  *
384  ** returns     : pointer to data element or NULL if queue is IsEmpty.
385  *
386  ** comments    :
387  *
388  ***/
389 
Q_PopHead(queue * q)390 void *Q_PopHead(queue *q)
391 {
392    datanode    *n;
393    void        *d;
394 
395    if(Q_IsEmpty(q))
396       return NULL;
397 
398    d = q->head->data;
399    n = q->head->next;
400    free(q->head);
401 
402    q->size--;
403 
404    if(q->size == 0)
405       q->head = q->tail = q->cursor = NULL;
406    else {
407       q->head = (node *)n;
408       q->head->prev = NULL;
409       q->cursor = q->head;
410    }
411 
412    q->sorted = False_;
413 
414    return d;
415 }
416 
417 
418 /***
419  *
420  ** function    : Q_PopTail
421  *
422  ** purpose     : remove element from tail of queue and return data.
423  *
424  ** parameters  : queue pointer
425  *
426  ** returns     : pointer to data element that was at tail. NULL if queue
427  *                IsEmpty.
428  *
429  ** comments    :
430  *
431  ***/
432 
Q_PopTail(queue * q)433 void *Q_PopTail(queue *q)
434 {
435    datanode    *p;
436    void        *d;
437 
438    if(Q_IsEmpty(q))
439       return NULL;
440 
441    d = q->tail->data;
442    p = q->tail->prev;
443    free(q->tail);
444    q->size--;
445 
446    if(q->size == 0)
447       q->head = q->tail = q->cursor = NULL;
448    else {
449       q->tail = (node *)p;
450       q->tail->next = NULL;
451       q->cursor = q->tail;
452    }
453 
454    q->sorted = False_;
455 
456    return d;
457 }
458 
459 
460 
461 /***
462  *
463  ** function    : Q_Next
464  *
465  ** purpose     : Move to the next element in the queue without popping
466  *
467  ** parameters  : queue pointer.
468  *
469  ** returns     : pointer to data element of new element or NULL if end
470  *                of the queue.
471  *
472  ** comments    : This uses the cursor for the current position. Q_Next
473  *                only moves in the direction from the head of the queue
474  *                to the tail.
475  ***/
476 
Q_Next(queue * q)477 void *Q_Next(queue *q)
478 {
479    if(!q)
480       return NULL;
481 
482    if(!q->cursor || q->cursor->next == NULL)
483       return NULL;
484 
485    q->cursor = (node *)q->cursor->next;
486 
487    return  q->cursor->data ;
488 }
489 
490 
491 
492 /***
493  *
494  ** function    : Q_Previous
495  *
496  ** purpose     : Opposite of Q_Next. Move to next element closer to the
497  *                head of the queue.
498  *
499  ** parameters  : pointer to queue
500  *
501  ** returns     : pointer to data of new element else NULL if queue IsEmpty
502  *
503  ** comments    : Makes cursor move towards the head of the queue.
504  *
505  ***/
506 
Q_Previous(queue * q)507 void *Q_Previous(queue *q)
508 {
509    if(!q)
510       return NULL;
511 
512    if(q->cursor->prev == NULL)
513       return NULL;
514 
515    q->cursor = (node *)q->cursor->prev;
516 
517    return q->cursor->data;
518 }
519 
520 
Q_Iter_Del(queue * q,q_iter iter)521 void *Q_Iter_Del(queue *q, q_iter iter)
522 {
523    void        *d;
524    datanode    *n, *p;
525 
526    if(!q)
527       return NULL;
528 
529    if(iter == NULL)
530       return NULL;
531 
532    if(iter == (q_iter)q->head)
533       return Q_PopHead(q);
534 
535    if(iter == (q_iter)q->tail)
536       return Q_PopTail(q);
537 
538    n = ((node*)iter)->next;
539    p = ((node*)iter)->prev;
540    d = ((node*)iter)->data;
541 
542    free(iter);
543 
544    if(p) {
545       p->next = n;
546    }
547    if (q->cursor == (node*)iter) {
548       if (p) {
549          q->cursor = p;
550       } else {
551          q->cursor = n;
552       }
553    }
554 
555 
556    if (n != NULL) {
557 	n->prev = p;
558    }
559 
560    q->size--;
561 
562    q->sorted = False_;
563 
564    return d;
565 }
566 
567 
568 
569 /***
570  *
571  ** function    : Q_DelCur
572  *
573  ** purpose     : Delete the current queue element as pointed to by
574  *                the cursor.
575  *
576  ** parameters  : queue pointer
577  *
578  ** returns     : pointer to data element.
579  *
580  ** comments    : WARNING! It is the responsibility of the caller to
581  *                free any memory. Queue cannot distinguish between
582  *                pointers to literals and malloced memory.
583  *
584  ***/
585 
Q_DelCur(queue * q)586 void *Q_DelCur(queue* q) {
587    if(q) {
588       return Q_Iter_Del(q, (q_iter)q->cursor);
589    }
590    return 0;
591 }
592 
593 
594 /***
595  *
596  ** function    : Q_Destroy
597  *
598  ** purpose     : Free all queue resources
599  *
600  ** parameters  : queue pointer
601  *
602  ** returns     : null.
603  *
604  ** comments    : WARNING! It is the responsibility of the caller to
605  *                free any memory. Queue cannot distinguish between
606  *                pointers to literals and malloced memory.
607  *
608  ***/
609 
Q_Destroy(queue * q)610 void Q_Destroy(queue *q)
611 {
612    while(!Q_IsEmpty(q)) {
613       Q_PopHead(q);
614    }
615 }
616 
617 
618 /***
619  *
620  ** function    : Q_Get
621  *
622  ** purpose     : get the pointer to the data at the cursor location
623  *
624  ** parameters  : queue pointer
625  *
626  ** returns     : data element pointer
627  *
628  ** comments    :
629  *
630  ***/
631 
Q_Get(queue * q)632 void *Q_Get(queue *q)
633 {
634    if(!q)
635       return NULL;
636 
637    if(q->cursor == NULL)
638       return NULL;
639    return q->cursor->data;
640 }
641 
642 
643 
644 /***
645  *
646  ** function    : Q_Put
647  *
648  ** purpose     : replace pointer to data with new pointer to data.
649  *
650  ** parameters  : queue pointer, data pointer
651  *
652  ** returns     : boolean- True_ if successful, False_ if cursor at NULL
653  *
654  ** comments    :
655  *
656  ***/
657 
Q_Put(queue * q,void * data)658 int Q_Put(queue *q, void *data)
659 {
660    if(q && data) {
661       if(q->cursor == NULL)
662          return False_;
663 
664       q->cursor->data = data;
665       return True_;
666    }
667    return False_;
668 }
669 
670 
671 /***
672  *
673  ** function    : Q_Find
674  *
675  ** purpose     : Linear search of queue for match with key in *data
676  *
677  ** parameters  : queue pointer q, data pointer with data containing key
678  *                comparison function here called Comp.
679  *
680  ** returns     : True_ if found , False_ if not in queue.
681  *
682  ** comments    : Useful for small queues that are constantly changing
683  *                and would otherwise need constant sorting with the
684  *                Q_Seek function.
685  *                For description of Comp see Q_Sort.
686  *                Queue cursor left on position found item else at end.
687  *
688  ***/
689 
Q_Find(queue * q,void * data,int (* Comp)(const void *,const void *))690 int Q_Find(queue *q, void *data,
691            int (*Comp)(const void *, const void *))
692 {
693    void *d;
694 
695    if (q == NULL) {
696 	return False_;
697    }
698 
699    d = Q_Head(q);
700    do {
701       if(Comp(d, data) == 0)
702          return True_;
703       d = Q_Next(q);
704    } while(!Q_AtTail(q));
705 
706    if(Comp(d, data) == 0)
707       return True_;
708 
709    return False_;
710 }
711 
712 /*========  Sorted Queue and Index functions   ========= */
713 
714 
QuickSort(void * list[],int low,int high,int (* Comp)(const void *,const void *))715 static void QuickSort(void *list[], int low, int high,
716                       int (*Comp)(const void *, const void *))
717 {
718    int     flag = 1, i, j;
719    void    *key, *temp;
720 
721    if(low < high) {
722       i = low;
723       j = high + 1;
724 
725       key = list[ low ];
726 
727       while(flag) {
728          i++;
729          while(Comp(list[i], key) < 0)
730             i++;
731 
732          j--;
733          while(Comp(list[j], key) > 0)
734             j--;
735 
736          if(i < j) {
737             temp = list[i];
738             list[i] = list[j];
739             list[j] = temp;
740          }
741          else  flag = 0;
742       }
743 
744       temp = list[low];
745       list[low] = list[j];
746       list[j] = temp;
747 
748       QuickSort(list, low, j-1, Comp);
749       QuickSort(list, j+1, high, Comp);
750    }
751 }
752 
753 
754 /***
755  *
756  ** function    : Q_Sort
757  *
758  ** purpose     : sort the queue and allow index style access.
759  *
760  ** parameters  : queue pointer, comparison function compatible with
761  *                with 'qsort'.
762  *
763  ** returns     : True_ if sort succeeded. False_ if error occurred.
764  *
765  ** comments    : Comp function supplied by caller must return
766  *                  -1 if data1  < data2
767  *                   0 if data1 == data2
768  *                  +1 if data1  > data2
769  *
770  *                    for Comp(data1, data2)
771  *
772  *                If queue is already sorted it frees the memory of the
773  *                old index and starts again.
774  *
775  ***/
776 
Q_Sort(queue * q,int (* Comp)(const void *,const void *))777 int Q_Sort(queue *q, int (*Comp)(const void *, const void *))
778 {
779    int         i;
780    void        *d;
781    datanode    *dn;
782 
783    /* if already sorted free memory for tag array */
784 
785    if(q->sorted) {
786       free(index);
787       free(posn_index);
788       q->sorted = False_;
789    }
790 
791    /* Now allocate memory of array, array of pointers */
792 
793    index = malloc(q->size * sizeof(q->cursor->data));
794    if(index == NULL)
795       return False_;
796 
797    posn_index = malloc(q->size * sizeof(q->cursor));
798    if(posn_index == NULL) {
799       free(index);
800       return False_;
801    }
802 
803    /* Walk queue putting pointers into array */
804 
805    d = Q_Head(q);
806    for(i=0; i < q->size; i++) {
807       index[i] = d;
808       posn_index[i] = q->cursor;
809       d = Q_Next(q);
810    }
811 
812    /* Now sort the index */
813 
814    QuickSort(index, 0, q->size - 1, Comp);
815 
816    /* Rearrange the actual queue into correct order */
817 
818    dn = q->head;
819    i = 0;
820    while(dn != NULL) {
821       dn->data = index[i++];
822       dn = dn->next;
823    }
824 
825    /* Re-position to original element */
826 
827    if(d != NULL)
828       Q_Find(q, d, Comp);
829    else  Q_Head(q);
830 
831    q->sorted = True_;
832 
833    return True_;
834 }
835 
836 
837 /***
838  *
839  ** function    : Q_BSearch
840  *
841  ** purpose     : binary search of queue index for node containing key
842  *
843  ** parameters  : queue pointer 'q', data pointer of key 'key',
844  *                  Comp comparison function.
845  *
846  ** returns     : integer index into array of node pointers,
847  *                or -1 if not found.
848  *
849  ** comments    : see Q_Sort for description of 'Comp' function.
850  *
851  ***/
852 
Q_BSearch(queue * q,void * key,int (* Comp)(const void *,const void *))853 static int Q_BSearch( queue *q, void *key,
854                       int (*Comp)(const void *, const void*))
855 {
856    int low, mid, hi, val;
857 
858    low = 0;
859    hi = q->size - 1;
860 
861    while(low <= hi) {
862       mid = (low + hi) / 2;
863       val = Comp(key, index[ mid ]);
864 
865       if(val < 0)
866          hi = mid - 1;
867 
868       else if(val > 0)
869          low = mid + 1;
870 
871       else /* Success */
872          return mid;
873    }
874 
875    /* Not Found */
876 
877    return -1;
878 }
879 
880 
881 /***
882  *
883  ** function    : Q_Seek
884  *
885  ** purpose     : use index to locate data according to key in 'data'
886  *
887  ** parameters  : queue pointer 'q', data pointer 'data', Comp comparison
888  *                function.
889  *
890  ** returns     : pointer to data or NULL if could not find it or could
891  *                not sort queue.
892  *
893  ** comments    : see Q_Sort for description of 'Comp' function.
894  *
895  ***/
896 
Q_Seek(queue * q,void * data,int (* Comp)(const void *,const void *))897 void *Q_Seek(queue *q, void *data, int (*Comp)(const void *, const void *))
898 {
899    int idx;
900 
901    if (q == NULL) {
902 	return NULL;
903    }
904 
905    if(!q->sorted) {
906       if(!Q_Sort(q, Comp))
907          return NULL;
908    }
909 
910    idx = Q_BSearch(q, data, Comp);
911 
912    if(idx < 0)
913       return NULL;
914 
915    q->cursor = posn_index[idx];
916 
917    return index[idx];
918 }
919 
920 
921 
922 /***
923  *
924  ** function    : Q_Insert
925  *
926  ** purpose     : Insert an element into an indexed queue
927  *
928  ** parameters  : queue pointer 'q', data pointer 'data', Comp comparison
929  *                function.
930  *
931  ** returns     : pointer to data or NULL if could not find it or could
932  *                not sort queue.
933  *
934  ** comments    : see Q_Sort for description of 'Comp' function.
935  *                WARNING! This code can be very slow since each new
936  *                element means a new Q_Sort.  Should only be used for
937  *                the insertion of the odd element ,not the piecemeal
938  *                building of an entire queue.
939  ***/
940 
Q_Insert(queue * q,void * data,int (* Comp)(const void *,const void *))941 int Q_Insert(queue *q, void *data, int (*Comp)(const void *, const void *))
942 {
943    if (q == NULL) {
944 	return False_;
945    }
946 
947    Q_PushHead(q, data);
948 
949    if(!Q_Sort(q, Comp))
950       return False_;
951 
952    return True_;
953 }
954 
955 /* read only funcs for iterating through queue. above funcs modify queue */
Q_Iter_Head(queue * q)956 q_iter Q_Iter_Head(queue *q) {
957    return q ? (q_iter)q->head : NULL;
958 }
959 
Q_Iter_Tail(queue * q)960 q_iter Q_Iter_Tail(queue *q) {
961    return q ? (q_iter)q->tail : NULL;
962 }
963 
Q_Iter_Next(q_iter qi)964 q_iter Q_Iter_Next(q_iter qi) {
965    return qi ? (q_iter)((node*)qi)->next : NULL;
966 }
967 
Q_Iter_Prev(q_iter qi)968 q_iter Q_Iter_Prev(q_iter qi) {
969    return qi ? (q_iter)((node*)qi)->prev : NULL;
970 }
971 
Q_Iter_Get(q_iter qi)972 void * Q_Iter_Get(q_iter qi) {
973    return qi ? ((node*)qi)->data : NULL;
974 }
975 
Q_Iter_Put(q_iter qi,void * data)976 int Q_Iter_Put(q_iter qi, void* data) {
977    if(qi) {
978       ((node*)qi)->data = data;
979       return True_;
980    }
981    return False_;
982 }
983