Reviewed by mjs.
[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 if (isLineTerminator()) {
281         nextLine();
282         state = InString;
283       } else {
284         record16(singleEscape(current));
285         state = InString;
286       }
287       break;
288     case InHexEscape:
289       if (isHexDigit(current) && isHexDigit(next1)) {
290         state = InString;
291         record16(convertHex(current, next1));
292         shift(1);
293       } else if (current == stringType) {
294         record16('x');
295         shift(1);
296         setDone(String);
297       } else {
298         record16('x');
299         record16(current);
300         state = InString;
301       }
302       break;
303     case InUnicodeEscape:
304       if (isHexDigit(current) && isHexDigit(next1) &&
305           isHexDigit(next2) && isHexDigit(next3)) {
306         record16(convertUnicode(current, next1, next2, next3));
307         shift(3);
308         state = InString;
309       } else if (current == stringType) {
310         record16('u');
311         shift(1);
312         setDone(String);
313       } else {
314         setDone(Bad);
315       }
316       break;
317     case InSingleLineComment:
318       if (isLineTerminator()) {
319         nextLine();
320         terminator = true;
321         if (restrKeyword) {
322           token = ';';
323           setDone(Other);
324         } else
325           state = Start;
326       } else if (current == 0) {
327         setDone(Eof);
328       }
329       break;
330     case InMultiLineComment:
331       if (current == 0) {
332         setDone(Bad);
333       } else if (isLineTerminator()) {
334         nextLine();
335       } else if (current == '*' && next1 == '/') {
336         state = Start;
337         shift(1);
338       }
339       break;
340     case InIdentifier:
341       if (isIdentLetter(current) || isDecimalDigit(current)) {
342         record16(current);
343         break;
344       }
345       setDone(Identifier);
346       break;
347     case InNum0:
348       if (current == 'x' || current == 'X') {
349         record8(current);
350         state = InHex;
351       } else if (current == '.') {
352         record8(current);
353         state = InDecimal;
354       } else if (current == 'e' || current == 'E') {
355         record8(current);
356         state = InExponentIndicator;
357       } else if (isOctalDigit(current)) {
358         record8(current);
359         state = InOctal;
360       } else if (isDecimalDigit(current)) {
361         record8(current);
362         state = InDecimal;
363       } else {
364         setDone(Number);
365       }
366       break;
367     case InHex:
368       if (isHexDigit(current)) {
369         record8(current);
370       } else {
371         setDone(Hex);
372       }
373       break;
374     case InOctal:
375       if (isOctalDigit(current)) {
376         record8(current);
377       }
378       else if (isDecimalDigit(current)) {
379         record8(current);
380         state = InDecimal;
381       } else
382         setDone(Octal);
383       break;
384     case InNum:
385       if (isDecimalDigit(current)) {
386         record8(current);
387       } else if (current == '.') {
388         record8(current);
389         state = InDecimal;
390       } else if (current == 'e' || current == 'E') {
391         record8(current);
392         state = InExponentIndicator;
393       } else
394         setDone(Number);
395       break;
396     case InDecimal:
397       if (isDecimalDigit(current)) {
398         record8(current);
399       } else if (current == 'e' || current == 'E') {
400         record8(current);
401         state = InExponentIndicator;
402       } else
403         setDone(Number);
404       break;
405     case InExponentIndicator:
406       if (current == '+' || current == '-') {
407         record8(current);
408       } else if (isDecimalDigit(current)) {
409         record8(current);
410         state = InExponent;
411       } else
412         setDone(Bad);
413       break;
414     case InExponent:
415       if (isDecimalDigit(current)) {
416         record8(current);
417       } else
418         setDone(Number);
419       break;
420     default:
421       assert(!"Unhandled state in switch statement");
422     }
423
424     // move on to the next character
425     if (!done)
426       shift(1);
427 #ifndef KJS_PURE_ECMA
428     if (state != Start && state != InSingleLineComment)
429       bol = false;
430 #endif
431   }
432
433   // no identifiers allowed directly after numeric literal, e.g. "3in" is bad
434   if ((state == Number || state == Octal || state == Hex)
435       && isIdentLetter(current))
436     state = Bad;
437
438   // terminate string
439   buffer8[pos8] = '\0';
440
441 #ifdef KJS_DEBUG_LEX
442   fprintf(stderr, "line: %d ", lineNo());
443   fprintf(stderr, "yytext (%x): ", buffer8[0]);
444   fprintf(stderr, "%s ", buffer8);
445 #endif
446
447   double dval = 0;
448   if (state == Number) {
449     dval = strtod(buffer8, 0L);
450   } else if (state == Hex) { // scan hex numbers
451     // TODO: support long unsigned int
452     unsigned int i;
453     sscanf(buffer8, "%x", &i);
454     dval = i;
455     state = Number;
456   } else if (state == Octal) {   // scan octal number
457     unsigned int ui;
458     sscanf(buffer8, "%o", &ui);
459     dval = ui;
460     state = Number;
461   }
462
463 #ifdef KJS_DEBUG_LEX
464   switch (state) {
465   case Eof:
466     printf("(EOF)\n");
467     break;
468   case Other:
469     printf("(Other)\n");
470     break;
471   case Identifier:
472     printf("(Identifier)/(Keyword)\n");
473     break;
474   case String:
475     printf("(String)\n");
476     break;
477   case Number:
478     printf("(Number)\n");
479     break;
480   default:
481     printf("(unknown)");
482   }
483 #endif
484
485   if (state != Identifier && eatNextIdentifier)
486     eatNextIdentifier = false;
487
488   restrKeyword = false;
489   delimited = false;
490   kjsyylloc.first_line = yylineno; // ???
491   kjsyylloc.last_line = yylineno;
492
493   switch (state) {
494   case Eof:
495     token = 0;
496     break;
497   case Other:
498     if(token == '}' || token == ';') {
499       delimited = true;
500     }
501     break;
502   case Identifier:
503     if ((token = Lookup::find(&mainTable, buffer16, pos16)) < 0) {
504       // Lookup for keyword failed, means this is an identifier
505       // Apply anonymous-function hack below (eat the identifier)
506       if (eatNextIdentifier) {
507         eatNextIdentifier = false;
508         token = lex();
509         break;
510       }
511       kjsyylval.ident = makeIdentifier(buffer16, pos16);
512       token = IDENT;
513       break;
514     }
515
516     eatNextIdentifier = false;
517     // Hack for "f = function somename() { ... }", too hard to get into the grammar
518     if (token == FUNCTION && lastToken == '=' )
519       eatNextIdentifier = true;
520
521     if (token == CONTINUE || token == BREAK ||
522         token == RETURN || token == THROW)
523       restrKeyword = true;
524     break;
525   case String:
526     kjsyylval.ustr = makeUString(buffer16, pos16);
527     token = STRING;
528     break;
529   case Number:
530     kjsyylval.dval = dval;
531     token = NUMBER;
532     break;
533   case Bad:
534     fprintf(stderr, "yylex: ERROR.\n");
535     return -1;
536   default:
537     assert(!"unhandled numeration value in switch");
538     return -1;
539   }
540   lastToken = token;
541   return token;
542 }
543
544 bool Lexer::isWhiteSpace() const
545 {
546   return (current == ' ' || current == '\t' ||
547           current == 0x0b || current == 0x0c || current == 0xa0);
548 }
549
550 bool Lexer::isLineTerminator()
551 {
552   bool cr = (current == '\r');
553   bool lf = (current == '\n');
554   if (cr)
555       skipLF = true;
556   else if (lf)
557       skipCR = true;
558   return cr || lf;
559 }
560
561 bool Lexer::isIdentLetter(unsigned short c)
562 {
563   /* TODO: allow other legitimate unicode chars */
564   return (c >= 'a' && c <= 'z' ||
565           c >= 'A' && c <= 'Z' ||
566           c == '$' || c == '_');
567 }
568
569 bool Lexer::isDecimalDigit(unsigned short c)
570 {
571   return (c >= '0' && c <= '9');
572 }
573
574 bool Lexer::isHexDigit(unsigned short c) const
575 {
576   return (c >= '0' && c <= '9' ||
577           c >= 'a' && c <= 'f' ||
578           c >= 'A' && c <= 'F');
579 }
580
581 bool Lexer::isOctalDigit(unsigned short c) const
582 {
583   return (c >= '0' && c <= '7');
584 }
585
586 int Lexer::matchPunctuator(unsigned short c1, unsigned short c2,
587                               unsigned short c3, unsigned short c4)
588 {
589   if (c1 == '>' && c2 == '>' && c3 == '>' && c4 == '=') {
590     shift(4);
591     return URSHIFTEQUAL;
592   } else if (c1 == '=' && c2 == '=' && c3 == '=') {
593     shift(3);
594     return STREQ;
595   } else if (c1 == '!' && c2 == '=' && c3 == '=') {
596     shift(3);
597     return STRNEQ;
598    } else if (c1 == '>' && c2 == '>' && c3 == '>') {
599     shift(3);
600     return URSHIFT;
601   } else if (c1 == '<' && c2 == '<' && c3 == '=') {
602     shift(3);
603     return LSHIFTEQUAL;
604   } else if (c1 == '>' && c2 == '>' && c3 == '=') {
605     shift(3);
606     return RSHIFTEQUAL;
607   } else if (c1 == '<' && c2 == '=') {
608     shift(2);
609     return LE;
610   } else if (c1 == '>' && c2 == '=') {
611     shift(2);
612     return GE;
613   } else if (c1 == '!' && c2 == '=') {
614     shift(2);
615     return NE;
616   } else if (c1 == '+' && c2 == '+') {
617     shift(2);
618     if (terminator)
619       return AUTOPLUSPLUS;
620     else
621       return PLUSPLUS;
622   } else if (c1 == '-' && c2 == '-') {
623     shift(2);
624     if (terminator)
625       return AUTOMINUSMINUS;
626     else
627       return MINUSMINUS;
628   } else if (c1 == '=' && c2 == '=') {
629     shift(2);
630     return EQEQ;
631   } else if (c1 == '+' && c2 == '=') {
632     shift(2);
633     return PLUSEQUAL;
634   } else if (c1 == '-' && c2 == '=') {
635     shift(2);
636     return MINUSEQUAL;
637   } else if (c1 == '*' && c2 == '=') {
638     shift(2);
639     return MULTEQUAL;
640   } else if (c1 == '/' && c2 == '=') {
641     shift(2);
642     return DIVEQUAL;
643   } else if (c1 == '&' && c2 == '=') {
644     shift(2);
645     return ANDEQUAL;
646   } else if (c1 == '^' && c2 == '=') {
647     shift(2);
648     return XOREQUAL;
649   } else if (c1 == '%' && c2 == '=') {
650     shift(2);
651     return MODEQUAL;
652   } else if (c1 == '|' && c2 == '=') {
653     shift(2);
654     return OREQUAL;
655   } else if (c1 == '<' && c2 == '<') {
656     shift(2);
657     return LSHIFT;
658   } else if (c1 == '>' && c2 == '>') {
659     shift(2);
660     return RSHIFT;
661   } else if (c1 == '&' && c2 == '&') {
662     shift(2);
663     return AND;
664   } else if (c1 == '|' && c2 == '|') {
665     shift(2);
666     return OR;
667   }
668
669   switch(c1) {
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     case '[':
692     case ']':
693     case ';':
694       shift(1);
695       return static_cast<int>(c1);
696     default:
697       return -1;
698   }
699 }
700
701 unsigned short Lexer::singleEscape(unsigned short c) const
702 {
703   switch(c) {
704   case 'b':
705     return 0x08;
706   case 't':
707     return 0x09;
708   case 'n':
709     return 0x0A;
710   case 'v':
711     return 0x0B;
712   case 'f':
713     return 0x0C;
714   case 'r':
715     return 0x0D;
716   case '"':
717     return 0x22;
718   case '\'':
719     return 0x27;
720   case '\\':
721     return 0x5C;
722   default:
723     return c;
724   }
725 }
726
727 unsigned short Lexer::convertOctal(unsigned short c1, unsigned short c2,
728                                       unsigned short c3) const
729 {
730   return ((c1 - '0') * 64 + (c2 - '0') * 8 + c3 - '0');
731 }
732
733 unsigned char Lexer::convertHex(unsigned short c)
734 {
735   if (c >= '0' && c <= '9')
736     return (c - '0');
737   else if (c >= 'a' && c <= 'f')
738     return (c - 'a' + 10);
739   else
740     return (c - 'A' + 10);
741 }
742
743 unsigned char Lexer::convertHex(unsigned short c1, unsigned short c2)
744 {
745   return ((convertHex(c1) << 4) + convertHex(c2));
746 }
747
748 UChar Lexer::convertUnicode(unsigned short c1, unsigned short c2,
749                                      unsigned short c3, unsigned short c4)
750 {
751   return UChar((convertHex(c1) << 4) + convertHex(c2),
752                (convertHex(c3) << 4) + convertHex(c4));
753 }
754
755 void Lexer::record8(unsigned short c)
756 {
757   assert(c <= 0xff);
758
759   // enlarge buffer if full
760   if (pos8 >= size8 - 1) {
761     char *tmp = new char[2 * size8];
762     memcpy(tmp, buffer8, size8 * sizeof(char));
763     delete [] buffer8;
764     buffer8 = tmp;
765     size8 *= 2;
766   }
767
768   buffer8[pos8++] = (char) c;
769 }
770
771 void Lexer::record16(UChar c)
772 {
773   // enlarge buffer if full
774   if (pos16 >= size16 - 1) {
775     UChar *tmp = new UChar[2 * size16];
776     memcpy(tmp, buffer16, size16 * sizeof(UChar));
777     delete [] buffer16;
778     buffer16 = tmp;
779     size16 *= 2;
780   }
781
782   buffer16[pos16++] = c;
783 }
784
785 bool Lexer::scanRegExp()
786 {
787   pos16 = 0;
788   bool lastWasEscape = false;
789   bool inBrackets = false;
790
791   while (1) {
792     if (isLineTerminator() || current == 0)
793       return false;
794     else if (current != '/' || lastWasEscape == true || inBrackets == true)
795     {
796         // keep track of '[' and ']'
797         if ( !lastWasEscape ) {
798           if ( current == '[' && !inBrackets )
799             inBrackets = true;
800           if ( current == ']' && inBrackets )
801             inBrackets = false;
802         }
803         record16(current);
804         lastWasEscape =
805             !lastWasEscape && (current == '\\');
806     }
807     else { // end of regexp
808       pattern = UString(buffer16, pos16);
809       pos16 = 0;
810       shift(1);
811       break;
812     }
813     shift(1);
814   }
815
816   while (isIdentLetter(current)) {
817     record16(current);
818     shift(1);
819   }
820   flags = UString(buffer16, pos16);
821
822   return true;
823 }
824
825
826 void Lexer::doneParsing()
827 {
828   for (unsigned i = 0; i < numIdentifiers; i++) {
829     delete identifiers[i];
830   }
831   free (identifiers);
832   identifiers = 0;
833   numIdentifiers = 0;
834   identifiersCapacity = 0;
835
836   for (unsigned i = 0; i < numStrings; i++) {
837     delete strings[i];
838   }
839   free (strings);
840   strings = 0;
841   numStrings = 0;
842   stringsCapacity = 0;
843 }
844
845 const int initialCapacity = 64;
846 const int growthFactor = 2;
847
848 Identifier *Lexer::makeIdentifier(UChar *buffer, unsigned int pos)
849 {
850   if (numIdentifiers == identifiersCapacity) {
851     identifiersCapacity = (identifiersCapacity == 0) ? initialCapacity : identifiersCapacity *growthFactor;
852     identifiers = (KJS::Identifier **)realloc(identifiers, sizeof(KJS::Identifier *) * identifiersCapacity);
853   }
854
855   KJS::Identifier *identifier = new KJS::Identifier(buffer16, pos16);
856   identifiers[numIdentifiers++] = identifier;
857   return identifier;
858 }
859  
860 UString *Lexer::makeUString(UChar *buffer, unsigned int pos)
861 {
862   if (numStrings == stringsCapacity) {
863     stringsCapacity = (stringsCapacity == 0) ? initialCapacity : stringsCapacity *growthFactor;
864     strings = (UString **)realloc(strings, sizeof(UString *) * stringsCapacity);
865   }
866
867   UString *string = new UString(buffer16, pos16);
868   strings[numStrings++] = string;
869   return string;
870 }