9fbcbf53eef5d02cb67e4c8210a92816fb476422
[WebKit-https.git] / JavaScriptCore / kjs / lexer.cpp
1 // -*- c-basic-offset: 2 -*-
2 /*
3  *  This file is part of the KDE libraries
4  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Library General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Library General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Library General Public License
17  *  along with this library; see the file COPYING.LIB.  If not, write to
18  *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  *  Boston, MA 02111-1307, USA.
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <ctype.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <assert.h>
32
33 #include "value.h"
34 #include "object.h"
35 #include "types.h"
36 #include "interpreter.h"
37 #include "nodes.h"
38 #include "lexer.h"
39 #include "identifier.h"
40 #include "lookup.h"
41 #include "internal.h"
42
43 // we can't specify the namespace in yacc's C output, so do it here
44 using namespace KJS;
45
46 static Lexer *currLexer = 0;
47
48 #ifndef KDE_USE_FINAL
49 #include "grammar.h"
50 #endif
51
52 #include "lexer.lut.h"
53
54 extern YYLTYPE kjsyylloc; // global bison variable holding token info
55
56 // a bridge for yacc from the C world to C++
57 int kjsyylex()
58 {
59   return Lexer::curr()->lex();
60 }
61
62 Lexer::Lexer()
63   : yylineno(1),
64     size8(128), size16(128), restrKeyword(false),
65     eatNextIdentifier(false), stackToken(-1), lastToken(-1), pos(0),
66     code(0), length(0),
67 #ifndef KJS_PURE_ECMA
68     bol(true),
69 #endif
70     current(0), next1(0), next2(0), next3(0),
71     strings(0), numStrings(0), stringsCapacity(0),
72     identifiers(0), numIdentifiers(0), identifiersCapacity(0)
73 {
74   // allocate space for read buffers
75   buffer8 = new char[size8];
76   buffer16 = new UChar[size16];
77   currLexer = this;
78 }
79
80 Lexer::~Lexer()
81 {
82   doneParsing();
83   delete [] buffer8;
84   delete [] buffer16;
85 }
86
87 Lexer *Lexer::curr()
88 {
89   if (!currLexer) {
90     // create singleton instance
91     currLexer = new Lexer();
92   }
93   return currLexer;
94 }
95
96 #ifdef KJS_DEBUG_MEM
97 void Lexer::globalClear()
98 {
99   delete currLexer;
100   currLexer = 0L;
101 }
102 #endif
103
104 void Lexer::setCode(const UString &sourceURL, int startingLineNumber, const UChar *c, unsigned int len)
105 {
106   yylineno = 1 + startingLineNumber;
107   m_sourceURL = sourceURL;
108   restrKeyword = false;
109   delimited = false;
110   eatNextIdentifier = false;
111   stackToken = -1;
112   lastToken = -1;
113   pos = 0;
114   code = c;
115   length = len;
116   skipLF = false;
117   skipCR = false;
118 #ifndef KJS_PURE_ECMA
119   bol = true;
120 #endif
121
122   // read first characters
123   current = (length > 0) ? code[0].uc : 0;
124   next1 = (length > 1) ? code[1].uc : 0;
125   next2 = (length > 2) ? code[2].uc : 0;
126   next3 = (length > 3) ? code[3].uc : 0;
127 }
128
129 void Lexer::shift(unsigned int p)
130 {
131   while (p--) {
132     pos++;
133     current = next1;
134     next1 = next2;
135     next2 = next3;
136     next3 = (pos + 3 < length) ? code[pos+3].uc : 0;
137   }
138 }
139
140 // called on each new line
141 void Lexer::nextLine()
142 {
143   yylineno++;
144 #ifndef KJS_PURE_ECMA
145   bol = true;
146 #endif
147 }
148
149 void Lexer::setDone(State s)
150 {
151   state = s;
152   done = true;
153 }
154
155 int Lexer::lex()
156 {
157   int token = 0;
158   state = Start;
159   unsigned short stringType = 0; // either single or double quotes
160   pos8 = pos16 = 0;
161   done = false;
162   terminator = false;
163   skipLF = false;
164   skipCR = false;
165
166   // did we push a token on the stack previously ?
167   // (after an automatic semicolon insertion)
168   if (stackToken >= 0) {
169     setDone(Other);
170     token = stackToken;
171     stackToken = 0;
172   }
173
174   while (!done) {
175     if (skipLF && current != '\n') // found \r but not \n afterwards
176         skipLF = false;
177     if (skipCR && current != '\r') // found \n but not \r afterwards
178         skipCR = false;
179     if (skipLF || skipCR) // found \r\n or \n\r -> eat the second one
180     {
181         skipLF = false;
182         skipCR = false;
183         shift(1);
184     }
185     switch (state) {
186     case Start:
187       if (isWhiteSpace()) {
188         // do nothing
189       } else if (current == '/' && next1 == '/') {
190         shift(1);
191         state = InSingleLineComment;
192       } else if (current == '/' && next1 == '*') {
193         shift(1);
194         state = InMultiLineComment;
195       } else if (current == 0) {
196         if (!terminator && !delimited) {
197           // automatic semicolon insertion if program incomplete
198           token = ';';
199           stackToken = 0;
200           setDone(Other);
201         } else
202           setDone(Eof);
203       } else if (isLineTerminator()) {
204         nextLine();
205         terminator = true;
206         if (restrKeyword) {
207           token = ';';
208           setDone(Other);
209         }
210       } else if (current == '"' || current == '\'') {
211         state = InString;
212         stringType = current;
213       } else if (isIdentLetter(current)) {
214         record16(current);
215         state = InIdentifier;
216       } else if (current == '0') {
217         record8(current);
218         state = InNum0;
219       } else if (isDecimalDigit(current)) {
220         record8(current);
221         state = InNum;
222       } else if (current == '.' && isDecimalDigit(next1)) {
223         record8(current);
224         state = InDecimal;
225 #ifndef KJS_PURE_ECMA
226         // <!-- marks the beginning of a line comment (for www usage)
227       } else if (current == '<' && next1 == '!' &&
228                  next2 == '-' && next3 == '-') {
229         shift(3);
230         state = InSingleLineComment;
231         // same for -->
232       } else if (bol && current == '-' && next1 == '-' &&  next2 == '>') {
233         shift(2);
234         state = InSingleLineComment;
235 #endif
236       } else {
237         token = matchPunctuator(current, next1, next2, next3);
238         if (token != -1) {
239           setDone(Other);
240         } else {
241           //      cerr << "encountered unknown character" << endl;
242           setDone(Bad);
243         }
244       }
245       break;
246     case InString:
247       if (current == stringType) {
248         shift(1);
249         setDone(String);
250       } else if (current == 0 || isLineTerminator()) {
251         setDone(Bad);
252       } else if (current == '\\') {
253         state = InEscapeSequence;
254       } else {
255         record16(current);
256       }
257       break;
258     // Escape Sequences inside of strings
259     case InEscapeSequence:
260       if (isOctalDigit(current)) {
261         if (current >= '0' && current <= '3' &&
262             isOctalDigit(next1) && isOctalDigit(next2)) {
263           record16(convertOctal(current, next1, next2));
264           shift(2);
265           state = InString;
266         } else if (isOctalDigit(current) && isOctalDigit(next1)) {
267           record16(convertOctal('0', current, next1));
268           shift(1);
269           state = InString;
270         } else if (isOctalDigit(current)) {
271           record16(convertOctal('0', '0', current));
272           state = InString;
273         } else {
274           setDone(Bad);
275         }
276       } else if (current == 'x')
277         state = InHexEscape;
278       else if (current == 'u')
279         state = InUnicodeEscape;
280       else {
281         record16(singleEscape(current));
282         state = InString;
283       }
284       break;
285     case InHexEscape:
286       if (isHexDigit(current) && isHexDigit(next1)) {
287         state = InString;
288         record16(convertHex(current, next1));
289         shift(1);
290       } else if (current == stringType) {
291         record16('x');
292         shift(1);
293         setDone(String);
294       } else {
295         record16('x');
296         record16(current);
297         state = InString;
298       }
299       break;
300     case InUnicodeEscape:
301       if (isHexDigit(current) && isHexDigit(next1) &&
302           isHexDigit(next2) && isHexDigit(next3)) {
303         record16(convertUnicode(current, next1, next2, next3));
304         shift(3);
305         state = InString;
306       } else if (current == stringType) {
307         record16('u');
308         shift(1);
309         setDone(String);
310       } else {
311         setDone(Bad);
312       }
313       break;
314     case InSingleLineComment:
315       if (isLineTerminator()) {
316         nextLine();
317         terminator = true;
318         if (restrKeyword) {
319           token = ';';
320           setDone(Other);
321         } else
322           state = Start;
323       } else if (current == 0) {
324         setDone(Eof);
325       }
326       break;
327     case InMultiLineComment:
328       if (current == 0) {
329         setDone(Bad);
330       } else if (isLineTerminator()) {
331         nextLine();
332       } else if (current == '*' && next1 == '/') {
333         state = Start;
334         shift(1);
335       }
336       break;
337     case InIdentifier:
338       if (isIdentLetter(current) || isDecimalDigit(current)) {
339         record16(current);
340         break;
341       }
342       setDone(Identifier);
343       break;
344     case InNum0:
345       if (current == 'x' || current == 'X') {
346         record8(current);
347         state = InHex;
348       } else if (current == '.') {
349         record8(current);
350         state = InDecimal;
351       } else if (current == 'e' || current == 'E') {
352         record8(current);
353         state = InExponentIndicator;
354       } else if (isOctalDigit(current)) {
355         record8(current);
356         state = InOctal;
357       } else if (isDecimalDigit(current)) {
358         record8(current);
359         state = InDecimal;
360       } else {
361         setDone(Number);
362       }
363       break;
364     case InHex:
365       if (isHexDigit(current)) {
366         record8(current);
367       } else {
368         setDone(Hex);
369       }
370       break;
371     case InOctal:
372       if (isOctalDigit(current)) {
373         record8(current);
374       }
375       else if (isDecimalDigit(current)) {
376         record8(current);
377         state = InDecimal;
378       } else
379         setDone(Octal);
380       break;
381     case InNum:
382       if (isDecimalDigit(current)) {
383         record8(current);
384       } else if (current == '.') {
385         record8(current);
386         state = InDecimal;
387       } else if (current == 'e' || current == 'E') {
388         record8(current);
389         state = InExponentIndicator;
390       } else
391         setDone(Number);
392       break;
393     case InDecimal:
394       if (isDecimalDigit(current)) {
395         record8(current);
396       } else if (current == 'e' || current == 'E') {
397         record8(current);
398         state = InExponentIndicator;
399       } else
400         setDone(Number);
401       break;
402     case InExponentIndicator:
403       if (current == '+' || current == '-') {
404         record8(current);
405       } else if (isDecimalDigit(current)) {
406         record8(current);
407         state = InExponent;
408       } else
409         setDone(Bad);
410       break;
411     case InExponent:
412       if (isDecimalDigit(current)) {
413         record8(current);
414       } else
415         setDone(Number);
416       break;
417     default:
418       assert(!"Unhandled state in switch statement");
419     }
420
421     // move on to the next character
422     if (!done)
423       shift(1);
424 #ifndef KJS_PURE_ECMA
425     if (state != Start && state != InSingleLineComment)
426       bol = false;
427 #endif
428   }
429
430   // no identifiers allowed directly after numeric literal, e.g. "3in" is bad
431   if ((state == Number || state == Octal || state == Hex)
432       && isIdentLetter(current))
433     state = Bad;
434
435   // terminate string
436   buffer8[pos8] = '\0';
437
438 #ifdef KJS_DEBUG_LEX
439   fprintf(stderr, "line: %d ", lineNo());
440   fprintf(stderr, "yytext (%x): ", buffer8[0]);
441   fprintf(stderr, "%s ", buffer8);
442 #endif
443
444   double dval = 0;
445   if (state == Number) {
446     dval = strtod(buffer8, 0L);
447   } else if (state == Hex) { // scan hex numbers
448     // TODO: support long unsigned int
449     unsigned int i;
450     sscanf(buffer8, "%x", &i);
451     dval = i;
452     state = Number;
453   } else if (state == Octal) {   // scan octal number
454     unsigned int ui;
455     sscanf(buffer8, "%o", &ui);
456     dval = ui;
457     state = Number;
458   }
459
460 #ifdef KJS_DEBUG_LEX
461   switch (state) {
462   case Eof:
463     printf("(EOF)\n");
464     break;
465   case Other:
466     printf("(Other)\n");
467     break;
468   case Identifier:
469     printf("(Identifier)/(Keyword)\n");
470     break;
471   case String:
472     printf("(String)\n");
473     break;
474   case Number:
475     printf("(Number)\n");
476     break;
477   default:
478     printf("(unknown)");
479   }
480 #endif
481
482   if (state != Identifier && eatNextIdentifier)
483     eatNextIdentifier = false;
484
485   restrKeyword = false;
486   delimited = false;
487   kjsyylloc.first_line = yylineno; // ???
488   kjsyylloc.last_line = yylineno;
489
490   switch (state) {
491   case Eof:
492     token = 0;
493     break;
494   case Other:
495     if(token == '}' || token == ';') {
496       delimited = true;
497     }
498     break;
499   case Identifier:
500     if ((token = Lookup::find(&mainTable, buffer16, pos16)) < 0) {
501       // Lookup for keyword failed, means this is an identifier
502       // Apply anonymous-function hack below (eat the identifier)
503       if (eatNextIdentifier) {
504         eatNextIdentifier = false;
505         token = lex();
506         break;
507       }
508       kjsyylval.ident = makeIdentifier(buffer16, pos16);
509       token = IDENT;
510       break;
511     }
512
513     eatNextIdentifier = false;
514     // Hack for "f = function somename() { ... }", too hard to get into the grammar
515     if (token == FUNCTION && lastToken == '=' )
516       eatNextIdentifier = true;
517
518     if (token == CONTINUE || token == BREAK ||
519         token == RETURN || token == THROW)
520       restrKeyword = true;
521     break;
522   case String:
523     kjsyylval.ustr = makeUString(buffer16, pos16);
524     token = STRING;
525     break;
526   case Number:
527     kjsyylval.dval = dval;
528     token = NUMBER;
529     break;
530   case Bad:
531     fprintf(stderr, "yylex: ERROR.\n");
532     return -1;
533   default:
534     assert(!"unhandled numeration value in switch");
535     return -1;
536   }
537   lastToken = token;
538   return token;
539 }
540
541 bool Lexer::isWhiteSpace() const
542 {
543   return (current == ' ' || current == '\t' ||
544           current == 0x0b || current == 0x0c || current == 0xa0);
545 }
546
547 bool Lexer::isLineTerminator()
548 {
549   bool cr = (current == '\r');
550   bool lf = (current == '\n');
551   if (cr)
552       skipLF = true;
553   else if (lf)
554       skipCR = true;
555   return cr || lf;
556 }
557
558 bool Lexer::isIdentLetter(unsigned short c)
559 {
560   /* TODO: allow other legitimate unicode chars */
561   return (c >= 'a' && c <= 'z' ||
562           c >= 'A' && c <= 'Z' ||
563           c == '$' || c == '_');
564 }
565
566 bool Lexer::isDecimalDigit(unsigned short c)
567 {
568   return (c >= '0' && c <= '9');
569 }
570
571 bool Lexer::isHexDigit(unsigned short c) const
572 {
573   return (c >= '0' && c <= '9' ||
574           c >= 'a' && c <= 'f' ||
575           c >= 'A' && c <= 'F');
576 }
577
578 bool Lexer::isOctalDigit(unsigned short c) const
579 {
580   return (c >= '0' && c <= '7');
581 }
582
583 int Lexer::matchPunctuator(unsigned short c1, unsigned short c2,
584                               unsigned short c3, unsigned short c4)
585 {
586   if (c1 == '>' && c2 == '>' && c3 == '>' && c4 == '=') {
587     shift(4);
588     return URSHIFTEQUAL;
589   } else if (c1 == '=' && c2 == '=' && c3 == '=') {
590     shift(3);
591     return STREQ;
592   } else if (c1 == '!' && c2 == '=' && c3 == '=') {
593     shift(3);
594     return STRNEQ;
595    } else if (c1 == '>' && c2 == '>' && c3 == '>') {
596     shift(3);
597     return URSHIFT;
598   } else if (c1 == '<' && c2 == '<' && c3 == '=') {
599     shift(3);
600     return LSHIFTEQUAL;
601   } else if (c1 == '>' && c2 == '>' && c3 == '=') {
602     shift(3);
603     return RSHIFTEQUAL;
604   } else if (c1 == '<' && c2 == '=') {
605     shift(2);
606     return LE;
607   } else if (c1 == '>' && c2 == '=') {
608     shift(2);
609     return GE;
610   } else if (c1 == '!' && c2 == '=') {
611     shift(2);
612     return NE;
613   } else if (c1 == '+' && c2 == '+') {
614     shift(2);
615     if (terminator)
616       return AUTOPLUSPLUS;
617     else
618       return PLUSPLUS;
619   } else if (c1 == '-' && c2 == '-') {
620     shift(2);
621     if (terminator)
622       return AUTOMINUSMINUS;
623     else
624       return MINUSMINUS;
625   } else if (c1 == '=' && c2 == '=') {
626     shift(2);
627     return EQEQ;
628   } else if (c1 == '+' && c2 == '=') {
629     shift(2);
630     return PLUSEQUAL;
631   } else if (c1 == '-' && c2 == '=') {
632     shift(2);
633     return MINUSEQUAL;
634   } else if (c1 == '*' && c2 == '=') {
635     shift(2);
636     return MULTEQUAL;
637   } else if (c1 == '/' && c2 == '=') {
638     shift(2);
639     return DIVEQUAL;
640   } else if (c1 == '&' && c2 == '=') {
641     shift(2);
642     return ANDEQUAL;
643   } else if (c1 == '^' && c2 == '=') {
644     shift(2);
645     return XOREQUAL;
646   } else if (c1 == '%' && c2 == '=') {
647     shift(2);
648     return MODEQUAL;
649   } else if (c1 == '|' && c2 == '=') {
650     shift(2);
651     return OREQUAL;
652   } else if (c1 == '<' && c2 == '<') {
653     shift(2);
654     return LSHIFT;
655   } else if (c1 == '>' && c2 == '>') {
656     shift(2);
657     return RSHIFT;
658   } else if (c1 == '&' && c2 == '&') {
659     shift(2);
660     return AND;
661   } else if (c1 == '|' && c2 == '|') {
662     shift(2);
663     return OR;
664   }
665
666   switch(c1) {
667     case '=':
668     case '>':
669     case '<':
670     case ',':
671     case '!':
672     case '~':
673     case '?':
674     case ':':
675     case '.':
676     case '+':
677     case '-':
678     case '*':
679     case '/':
680     case '&':
681     case '|':
682     case '^':
683     case '%':
684     case '(':
685     case ')':
686     case '{':
687     case '}':
688     case '[':
689     case ']':
690     case ';':
691       shift(1);
692       return static_cast<int>(c1);
693     default:
694       return -1;
695   }
696 }
697
698 unsigned short Lexer::singleEscape(unsigned short c) const
699 {
700   switch(c) {
701   case 'b':
702     return 0x08;
703   case 't':
704     return 0x09;
705   case 'n':
706     return 0x0A;
707   case 'v':
708     return 0x0B;
709   case 'f':
710     return 0x0C;
711   case 'r':
712     return 0x0D;
713   case '"':
714     return 0x22;
715   case '\'':
716     return 0x27;
717   case '\\':
718     return 0x5C;
719   default:
720     return c;
721   }
722 }
723
724 unsigned short Lexer::convertOctal(unsigned short c1, unsigned short c2,
725                                       unsigned short c3) const
726 {
727   return ((c1 - '0') * 64 + (c2 - '0') * 8 + c3 - '0');
728 }
729
730 unsigned char Lexer::convertHex(unsigned short c)
731 {
732   if (c >= '0' && c <= '9')
733     return (c - '0');
734   else if (c >= 'a' && c <= 'f')
735     return (c - 'a' + 10);
736   else
737     return (c - 'A' + 10);
738 }
739
740 unsigned char Lexer::convertHex(unsigned short c1, unsigned short c2)
741 {
742   return ((convertHex(c1) << 4) + convertHex(c2));
743 }
744
745 UChar Lexer::convertUnicode(unsigned short c1, unsigned short c2,
746                                      unsigned short c3, unsigned short c4)
747 {
748   return UChar((convertHex(c1) << 4) + convertHex(c2),
749                (convertHex(c3) << 4) + convertHex(c4));
750 }
751
752 void Lexer::record8(unsigned short c)
753 {
754   assert(c <= 0xff);
755
756   // enlarge buffer if full
757   if (pos8 >= size8 - 1) {
758     char *tmp = new char[2 * size8];
759     memcpy(tmp, buffer8, size8 * sizeof(char));
760     delete [] buffer8;
761     buffer8 = tmp;
762     size8 *= 2;
763   }
764
765   buffer8[pos8++] = (char) c;
766 }
767
768 void Lexer::record16(UChar c)
769 {
770   // enlarge buffer if full
771   if (pos16 >= size16 - 1) {
772     UChar *tmp = new UChar[2 * size16];
773     memcpy(tmp, buffer16, size16 * sizeof(UChar));
774     delete [] buffer16;
775     buffer16 = tmp;
776     size16 *= 2;
777   }
778
779   buffer16[pos16++] = c;
780 }
781
782 bool Lexer::scanRegExp()
783 {
784   pos16 = 0;
785   bool lastWasEscape = false;
786   bool inBrackets = false;
787
788   while (1) {
789     if (isLineTerminator() || current == 0)
790       return false;
791     else if (current != '/' || lastWasEscape == true || inBrackets == true)
792     {
793         // keep track of '[' and ']'
794         if ( !lastWasEscape ) {
795           if ( current == '[' && !inBrackets )
796             inBrackets = true;
797           if ( current == ']' && inBrackets )
798             inBrackets = false;
799         }
800         record16(current);
801         lastWasEscape =
802             !lastWasEscape && (current == '\\');
803     }
804     else { // end of regexp
805       pattern = UString(buffer16, pos16);
806       pos16 = 0;
807       shift(1);
808       break;
809     }
810     shift(1);
811   }
812
813   while (isIdentLetter(current)) {
814     record16(current);
815     shift(1);
816   }
817   flags = UString(buffer16, pos16);
818
819   return true;
820 }
821
822
823 void Lexer::doneParsing()
824 {
825   for (unsigned i = 0; i < numIdentifiers; i++) {
826     delete identifiers[i];
827   }
828   free (identifiers);
829   identifiers = 0;
830   numIdentifiers = 0;
831   identifiersCapacity = 0;
832
833   for (unsigned i = 0; i < numStrings; i++) {
834     delete strings[i];
835   }
836   free (strings);
837   strings = 0;
838   numStrings = 0;
839   stringsCapacity = 0;
840 }
841
842 const int initialCapacity = 64;
843 const int growthFactor = 2;
844
845 Identifier *Lexer::makeIdentifier(UChar *buffer, unsigned int pos)
846 {
847   if (numIdentifiers == identifiersCapacity) {
848     identifiersCapacity = (identifiersCapacity == 0) ? initialCapacity : identifiersCapacity *growthFactor;
849     identifiers = (KJS::Identifier **)realloc(identifiers, sizeof(KJS::Identifier *) * identifiersCapacity);
850   }
851
852   KJS::Identifier *identifier = new KJS::Identifier(buffer16, pos16);
853   identifiers[numIdentifiers++] = identifier;
854   return identifier;
855 }
856  
857 UString *Lexer::makeUString(UChar *buffer, unsigned int pos)
858 {
859   if (numStrings == stringsCapacity) {
860     stringsCapacity = (stringsCapacity == 0) ? initialCapacity : stringsCapacity *growthFactor;
861     strings = (UString **)realloc(strings, sizeof(UString *) * stringsCapacity);
862   }
863
864   UString *string = new UString(buffer16, pos16);
865   strings[numStrings++] = string;
866   return string;
867 }