Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(919)

Side by Side Diff: third_party/jsoncpp/overrides/src/lib_json/json_reader.cpp

Issue 98713004: NaCl: Update revision in DEPS, r12488 -> r12497 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Add <string> and <iostream> to json_reader.cpp (or rather, copy the file into the overrides directo… Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « DEPS ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2007-2011 Baptiste Lepilleur
2 // Distributed under MIT license, or public domain if desired and
3 // recognized in your jurisdiction.
4 // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
5
6 #if !defined(JSON_IS_AMALGAMATION)
7 # include <json/assertions.h>
8 # include <json/reader.h>
9 # include <json/value.h>
10 # include "json_tool.h"
11 #endif // if !defined(JSON_IS_AMALGAMATION)
12 #include <utility>
13 #include <cstdio>
14 #include <cassert>
15 #include <cstring>
16 #include <stdexcept>
17 #include <string>
18 #include <iostream>
JF 2013/12/06 16:53:29 The above two lines are new.
19
20 #if _MSC_VER >= 1400 // VC++ 8.0
21 #pragma warning( disable : 4996 ) // disable warning about strdup being deprec ated.
22 #endif
23
24 namespace Json {
25
26 // Implementation of class Features
27 // ////////////////////////////////
28
29 Features::Features()
30 : allowComments_( true )
31 , strictRoot_( false )
32 {
33 }
34
35
36 Features
37 Features::all()
38 {
39 return Features();
40 }
41
42
43 Features
44 Features::strictMode()
45 {
46 Features features;
47 features.allowComments_ = false;
48 features.strictRoot_ = true;
49 return features;
50 }
51
52 // Implementation of class Reader
53 // ////////////////////////////////
54
55
56 static inline bool
57 in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::C har c4 )
58 {
59 return c == c1 || c == c2 || c == c3 || c == c4;
60 }
61
62 static inline bool
63 in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::C har c4, Reader::Char c5 )
64 {
65 return c == c1 || c == c2 || c == c3 || c == c4 || c == c5;
66 }
67
68
69 static bool
70 containsNewLine( Reader::Location begin,
71 Reader::Location end )
72 {
73 for ( ;begin < end; ++begin )
74 if ( *begin == '\n' || *begin == '\r' )
75 return true;
76 return false;
77 }
78
79
80 // Class Reader
81 // //////////////////////////////////////////////////////////////////
82
83 Reader::Reader()
84 : errors_(),
85 document_(),
86 begin_(),
87 end_(),
88 current_(),
89 lastValueEnd_(),
90 lastValue_(),
91 commentsBefore_(),
92 features_( Features::all() ),
93 collectComments_()
94 {
95 }
96
97
98 Reader::Reader( const Features &features )
99 : errors_(),
100 document_(),
101 begin_(),
102 end_(),
103 current_(),
104 lastValueEnd_(),
105 lastValue_(),
106 commentsBefore_(),
107 features_( features ),
108 collectComments_()
109 {
110 }
111
112
113 bool
114 Reader::parse( const std::string &document,
115 Value &root,
116 bool collectComments )
117 {
118 document_ = document;
119 const char *begin = document_.c_str();
120 const char *end = begin + document_.length();
121 return parse( begin, end, root, collectComments );
122 }
123
124
125 bool
126 Reader::parse( std::istream& sin,
127 Value &root,
128 bool collectComments )
129 {
130 //std::istream_iterator<char> begin(sin);
131 //std::istream_iterator<char> end;
132 // Those would allow streamed input from a file, if parse() were a
133 // template function.
134
135 // Since std::string is reference-counted, this at least does not
136 // create an extra copy.
137 std::string doc;
138 std::getline(sin, doc, (char)EOF);
139 return parse( doc, root, collectComments );
140 }
141
142 bool
143 Reader::parse( const char *beginDoc, const char *endDoc,
144 Value &root,
145 bool collectComments )
146 {
147 if ( !features_.allowComments_ )
148 {
149 collectComments = false;
150 }
151
152 begin_ = beginDoc;
153 end_ = endDoc;
154 collectComments_ = collectComments;
155 current_ = begin_;
156 lastValueEnd_ = 0;
157 lastValue_ = 0;
158 commentsBefore_ = "";
159 errors_.clear();
160 while ( !nodes_.empty() )
161 nodes_.pop();
162 nodes_.push( &root );
163
164 bool successful = readValue();
165 Token token;
166 skipCommentTokens( token );
167 if ( collectComments_ && !commentsBefore_.empty() )
168 root.setComment( commentsBefore_, commentAfter );
169 if ( features_.strictRoot_ )
170 {
171 if ( !root.isArray() && !root.isObject() )
172 {
173 // Set error location to start of doc, ideally should be first token fo und in doc
174 token.type_ = tokenError;
175 token.start_ = beginDoc;
176 token.end_ = endDoc;
177 addError( "A valid JSON document must be either an array or an object v alue.",
178 token );
179 return false;
180 }
181 }
182 return successful;
183 }
184
185
186 bool
187 Reader::readValue()
188 {
189 Token token;
190 skipCommentTokens( token );
191 bool successful = true;
192
193 if ( collectComments_ && !commentsBefore_.empty() )
194 {
195 currentValue().setComment( commentsBefore_, commentBefore );
196 commentsBefore_ = "";
197 }
198
199
200 switch ( token.type_ )
201 {
202 case tokenObjectBegin:
203 successful = readObject( token );
204 break;
205 case tokenArrayBegin:
206 successful = readArray( token );
207 break;
208 case tokenNumber:
209 successful = decodeNumber( token );
210 break;
211 case tokenString:
212 successful = decodeString( token );
213 break;
214 case tokenTrue:
215 currentValue() = true;
216 break;
217 case tokenFalse:
218 currentValue() = false;
219 break;
220 case tokenNull:
221 currentValue() = Value();
222 break;
223 default:
224 return addError( "Syntax error: value, object or array expected.", token ) ;
225 }
226
227 if ( collectComments_ )
228 {
229 lastValueEnd_ = current_;
230 lastValue_ = &currentValue();
231 }
232
233 return successful;
234 }
235
236
237 void
238 Reader::skipCommentTokens( Token &token )
239 {
240 if ( features_.allowComments_ )
241 {
242 do
243 {
244 readToken( token );
245 }
246 while ( token.type_ == tokenComment );
247 }
248 else
249 {
250 readToken( token );
251 }
252 }
253
254
255 bool
256 Reader::expectToken( TokenType type, Token &token, const char *message )
257 {
258 readToken( token );
259 if ( token.type_ != type )
260 return addError( message, token );
261 return true;
262 }
263
264
265 bool
266 Reader::readToken( Token &token )
267 {
268 skipSpaces();
269 token.start_ = current_;
270 Char c = getNextChar();
271 bool ok = true;
272 switch ( c )
273 {
274 case '{':
275 token.type_ = tokenObjectBegin;
276 break;
277 case '}':
278 token.type_ = tokenObjectEnd;
279 break;
280 case '[':
281 token.type_ = tokenArrayBegin;
282 break;
283 case ']':
284 token.type_ = tokenArrayEnd;
285 break;
286 case '"':
287 token.type_ = tokenString;
288 ok = readString();
289 break;
290 case '/':
291 token.type_ = tokenComment;
292 ok = readComment();
293 break;
294 case '0':
295 case '1':
296 case '2':
297 case '3':
298 case '4':
299 case '5':
300 case '6':
301 case '7':
302 case '8':
303 case '9':
304 case '-':
305 token.type_ = tokenNumber;
306 readNumber();
307 break;
308 case 't':
309 token.type_ = tokenTrue;
310 ok = match( "rue", 3 );
311 break;
312 case 'f':
313 token.type_ = tokenFalse;
314 ok = match( "alse", 4 );
315 break;
316 case 'n':
317 token.type_ = tokenNull;
318 ok = match( "ull", 3 );
319 break;
320 case ',':
321 token.type_ = tokenArraySeparator;
322 break;
323 case ':':
324 token.type_ = tokenMemberSeparator;
325 break;
326 case 0:
327 token.type_ = tokenEndOfStream;
328 break;
329 default:
330 ok = false;
331 break;
332 }
333 if ( !ok )
334 token.type_ = tokenError;
335 token.end_ = current_;
336 return true;
337 }
338
339
340 void
341 Reader::skipSpaces()
342 {
343 while ( current_ != end_ )
344 {
345 Char c = *current_;
346 if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' )
347 ++current_;
348 else
349 break;
350 }
351 }
352
353
354 bool
355 Reader::match( Location pattern,
356 int patternLength )
357 {
358 if ( end_ - current_ < patternLength )
359 return false;
360 int index = patternLength;
361 while ( index-- )
362 if ( current_[index] != pattern[index] )
363 return false;
364 current_ += patternLength;
365 return true;
366 }
367
368
369 bool
370 Reader::readComment()
371 {
372 Location commentBegin = current_ - 1;
373 Char c = getNextChar();
374 bool successful = false;
375 if ( c == '*' )
376 successful = readCStyleComment();
377 else if ( c == '/' )
378 successful = readCppStyleComment();
379 if ( !successful )
380 return false;
381
382 if ( collectComments_ )
383 {
384 CommentPlacement placement = commentBefore;
385 if ( lastValueEnd_ && !containsNewLine( lastValueEnd_, commentBegin ) )
386 {
387 if ( c != '*' || !containsNewLine( commentBegin, current_ ) )
388 placement = commentAfterOnSameLine;
389 }
390
391 addComment( commentBegin, current_, placement );
392 }
393 return true;
394 }
395
396
397 void
398 Reader::addComment( Location begin,
399 Location end,
400 CommentPlacement placement )
401 {
402 assert( collectComments_ );
403 if ( placement == commentAfterOnSameLine )
404 {
405 assert( lastValue_ != 0 );
406 lastValue_->setComment( std::string( begin, end ), placement );
407 }
408 else
409 {
410 if ( !commentsBefore_.empty() )
411 commentsBefore_ += "\n";
412 commentsBefore_ += std::string( begin, end );
413 }
414 }
415
416
417 bool
418 Reader::readCStyleComment()
419 {
420 while ( current_ != end_ )
421 {
422 Char c = getNextChar();
423 if ( c == '*' && *current_ == '/' )
424 break;
425 }
426 return getNextChar() == '/';
427 }
428
429
430 bool
431 Reader::readCppStyleComment()
432 {
433 while ( current_ != end_ )
434 {
435 Char c = getNextChar();
436 if ( c == '\r' || c == '\n' )
437 break;
438 }
439 return true;
440 }
441
442
443 void
444 Reader::readNumber()
445 {
446 while ( current_ != end_ )
447 {
448 if ( !(*current_ >= '0' && *current_ <= '9') &&
449 !in( *current_, '.', 'e', 'E', '+', '-' ) )
450 break;
451 ++current_;
452 }
453 }
454
455 bool
456 Reader::readString()
457 {
458 Char c = 0;
459 while ( current_ != end_ )
460 {
461 c = getNextChar();
462 if ( c == '\\' )
463 getNextChar();
464 else if ( c == '"' )
465 break;
466 }
467 return c == '"';
468 }
469
470
471 bool
472 Reader::readObject( Token &/*tokenStart*/ )
473 {
474 Token tokenName;
475 std::string name;
476 currentValue() = Value( objectValue );
477 while ( readToken( tokenName ) )
478 {
479 bool initialTokenOk = true;
480 while ( tokenName.type_ == tokenComment && initialTokenOk )
481 initialTokenOk = readToken( tokenName );
482 if ( !initialTokenOk )
483 break;
484 if ( tokenName.type_ == tokenObjectEnd && name.empty() ) // empty objec t
485 return true;
486 if ( tokenName.type_ != tokenString )
487 break;
488
489 name = "";
490 if ( !decodeString( tokenName, name ) )
491 return recoverFromError( tokenObjectEnd );
492
493 Token colon;
494 if ( !readToken( colon ) || colon.type_ != tokenMemberSeparator )
495 {
496 return addErrorAndRecover( "Missing ':' after object member name",
497 colon,
498 tokenObjectEnd );
499 }
500 Value &value = currentValue()[ name ];
501 nodes_.push( &value );
502 bool ok = readValue();
503 nodes_.pop();
504 if ( !ok ) // error already set
505 return recoverFromError( tokenObjectEnd );
506
507 Token comma;
508 if ( !readToken( comma )
509 || ( comma.type_ != tokenObjectEnd &&
510 comma.type_ != tokenArraySeparator &&
511 comma.type_ != tokenComment ) )
512 {
513 return addErrorAndRecover( "Missing ',' or '}' in object declaration",
514 comma,
515 tokenObjectEnd );
516 }
517 bool finalizeTokenOk = true;
518 while ( comma.type_ == tokenComment &&
519 finalizeTokenOk )
520 finalizeTokenOk = readToken( comma );
521 if ( comma.type_ == tokenObjectEnd )
522 return true;
523 }
524 return addErrorAndRecover( "Missing '}' or object member name",
525 tokenName,
526 tokenObjectEnd );
527 }
528
529
530 bool
531 Reader::readArray( Token &/*tokenStart*/ )
532 {
533 currentValue() = Value( arrayValue );
534 skipSpaces();
535 if ( *current_ == ']' ) // empty array
536 {
537 Token endArray;
538 readToken( endArray );
539 return true;
540 }
541 int index = 0;
542 for (;;)
543 {
544 Value &value = currentValue()[ index++ ];
545 nodes_.push( &value );
546 bool ok = readValue();
547 nodes_.pop();
548 if ( !ok ) // error already set
549 return recoverFromError( tokenArrayEnd );
550
551 Token token;
552 // Accept Comment after last item in the array.
553 ok = readToken( token );
554 while ( token.type_ == tokenComment && ok )
555 {
556 ok = readToken( token );
557 }
558 bool badTokenType = ( token.type_ != tokenArraySeparator &&
559 token.type_ != tokenArrayEnd );
560 if ( !ok || badTokenType )
561 {
562 return addErrorAndRecover( "Missing ',' or ']' in array declaration",
563 token,
564 tokenArrayEnd );
565 }
566 if ( token.type_ == tokenArrayEnd )
567 break;
568 }
569 return true;
570 }
571
572
573 bool
574 Reader::decodeNumber( Token &token )
575 {
576 bool isDouble = false;
577 for ( Location inspect = token.start_; inspect != token.end_; ++inspect )
578 {
579 isDouble = isDouble
580 || in( *inspect, '.', 'e', 'E', '+' )
581 || ( *inspect == '-' && inspect != token.start_ );
582 }
583 if ( isDouble )
584 return decodeDouble( token );
585 // Attempts to parse the number as an integer. If the number is
586 // larger than the maximum supported value of an integer then
587 // we decode the number as a double.
588 Location current = token.start_;
589 bool isNegative = *current == '-';
590 if ( isNegative )
591 ++current;
592 Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(-Value:: minLargestInt)
593 : Value::maxLargestUInt;
594 Value::LargestUInt threshold = maxIntegerValue / 10;
595 Value::LargestUInt value = 0;
596 while ( current < token.end_ )
597 {
598 Char c = *current++;
599 if ( c < '0' || c > '9' )
600 return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
601 Value::UInt digit(c - '0');
602 if ( value >= threshold )
603 {
604 // We've hit or exceeded the max value divided by 10 (rounded down). If
605 // a) we've only just touched the limit, b) this is the last digit, and
606 // c) it's small enough to fit in that rounding delta, we're okay.
607 // Otherwise treat this number as a double to avoid overflow.
608 if (value > threshold ||
609 current != token.end_ ||
610 digit > maxIntegerValue % 10)
611 {
612 return decodeDouble( token );
613 }
614 }
615 value = value * 10 + digit;
616 }
617 if ( isNegative )
618 currentValue() = -Value::LargestInt( value );
619 else if ( value <= Value::LargestUInt(Value::maxInt) )
620 currentValue() = Value::LargestInt( value );
621 else
622 currentValue() = value;
623 return true;
624 }
625
626
627 bool
628 Reader::decodeDouble( Token &token )
629 {
630 double value = 0;
631 const int bufferSize = 32;
632 int count;
633 int length = int(token.end_ - token.start_);
634
635 // Sanity check to avoid buffer overflow exploits.
636 if (length < 0) {
637 return addError( "Unable to parse token length", token );
638 }
639
640 // Avoid using a string constant for the format control string given to
641 // sscanf, as this can cause hard to debug crashes on OS X. See here for more
642 // info:
643 //
644 // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/g cc-4.0.1/gcc/Incompatibilities.html
645 char format[] = "%lf";
646
647 if ( length <= bufferSize )
648 {
649 Char buffer[bufferSize+1];
650 memcpy( buffer, token.start_, length );
651 buffer[length] = 0;
652 count = sscanf( buffer, format, &value );
653 }
654 else
655 {
656 std::string buffer( token.start_, token.end_ );
657 count = sscanf( buffer.c_str(), format, &value );
658 }
659
660 if ( count != 1 )
661 return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
662 currentValue() = value;
663 return true;
664 }
665
666
667 bool
668 Reader::decodeString( Token &token )
669 {
670 std::string decoded;
671 if ( !decodeString( token, decoded ) )
672 return false;
673 currentValue() = decoded;
674 return true;
675 }
676
677
678 bool
679 Reader::decodeString( Token &token, std::string &decoded )
680 {
681 decoded.reserve( token.end_ - token.start_ - 2 );
682 Location current = token.start_ + 1; // skip '"'
683 Location end = token.end_ - 1; // do not include '"'
684 while ( current != end )
685 {
686 Char c = *current++;
687 if ( c == '"' )
688 break;
689 else if ( c == '\\' )
690 {
691 if ( current == end )
692 return addError( "Empty escape sequence in string", token, current ) ;
693 Char escape = *current++;
694 switch ( escape )
695 {
696 case '"': decoded += '"'; break;
697 case '/': decoded += '/'; break;
698 case '\\': decoded += '\\'; break;
699 case 'b': decoded += '\b'; break;
700 case 'f': decoded += '\f'; break;
701 case 'n': decoded += '\n'; break;
702 case 'r': decoded += '\r'; break;
703 case 't': decoded += '\t'; break;
704 case 'u':
705 {
706 unsigned int unicode;
707 if ( !decodeUnicodeCodePoint( token, current, end, unicode ) )
708 return false;
709 decoded += codePointToUTF8(unicode);
710 }
711 break;
712 default:
713 return addError( "Bad escape sequence in string", token, current );
714 }
715 }
716 else
717 {
718 decoded += c;
719 }
720 }
721 return true;
722 }
723
724 bool
725 Reader::decodeUnicodeCodePoint( Token &token,
726 Location &current,
727 Location end,
728 unsigned int &unicode )
729 {
730
731 if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) )
732 return false;
733 if (unicode >= 0xD800 && unicode <= 0xDBFF)
734 {
735 // surrogate pairs
736 if (end - current < 6)
737 return addError( "additional six characters expected to parse unicode s urrogate pair.", token, current );
738 unsigned int surrogatePair;
739 if (*(current++) == '\\' && *(current++)== 'u')
740 {
741 if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair ))
742 {
743 unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3 FF);
744 }
745 else
746 return false;
747 }
748 else
749 return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current );
750 }
751 return true;
752 }
753
754 bool
755 Reader::decodeUnicodeEscapeSequence( Token &token,
756 Location &current,
757 Location end,
758 unsigned int &unicode )
759 {
760 if ( end - current < 4 )
761 return addError( "Bad unicode escape sequence in string: four digits expec ted.", token, current );
762 unicode = 0;
763 for ( int index =0; index < 4; ++index )
764 {
765 Char c = *current++;
766 unicode *= 16;
767 if ( c >= '0' && c <= '9' )
768 unicode += c - '0';
769 else if ( c >= 'a' && c <= 'f' )
770 unicode += c - 'a' + 10;
771 else if ( c >= 'A' && c <= 'F' )
772 unicode += c - 'A' + 10;
773 else
774 return addError( "Bad unicode escape sequence in string: hexadecimal di git expected.", token, current );
775 }
776 return true;
777 }
778
779
780 bool
781 Reader::addError( const std::string &message,
782 Token &token,
783 Location extra )
784 {
785 ErrorInfo info;
786 info.token_ = token;
787 info.message_ = message;
788 info.extra_ = extra;
789 errors_.push_back( info );
790 return false;
791 }
792
793
794 bool
795 Reader::recoverFromError( TokenType skipUntilToken )
796 {
797 int errorCount = int(errors_.size());
798 Token skip;
799 for (;;)
800 {
801 if ( !readToken(skip) )
802 errors_.resize( errorCount ); // discard errors caused by recovery
803 if ( skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream )
804 break;
805 }
806 errors_.resize( errorCount );
807 return false;
808 }
809
810
811 bool
812 Reader::addErrorAndRecover( const std::string &message,
813 Token &token,
814 TokenType skipUntilToken )
815 {
816 addError( message, token );
817 return recoverFromError( skipUntilToken );
818 }
819
820
821 Value &
822 Reader::currentValue()
823 {
824 return *(nodes_.top());
825 }
826
827
828 Reader::Char
829 Reader::getNextChar()
830 {
831 if ( current_ == end_ )
832 return 0;
833 return *current_++;
834 }
835
836
837 void
838 Reader::getLocationLineAndColumn( Location location,
839 int &line,
840 int &column ) const
841 {
842 Location current = begin_;
843 Location lastLineStart = current;
844 line = 0;
845 while ( current < location && current != end_ )
846 {
847 Char c = *current++;
848 if ( c == '\r' )
849 {
850 if ( *current == '\n' )
851 ++current;
852 lastLineStart = current;
853 ++line;
854 }
855 else if ( c == '\n' )
856 {
857 lastLineStart = current;
858 ++line;
859 }
860 }
861 // column & line start at 1
862 column = int(location - lastLineStart) + 1;
863 ++line;
864 }
865
866
867 std::string
868 Reader::getLocationLineAndColumn( Location location ) const
869 {
870 int line, column;
871 getLocationLineAndColumn( location, line, column );
872 char buffer[18+16+16+1];
873 sprintf( buffer, "Line %d, Column %d", line, column );
874 return buffer;
875 }
876
877
878 // Deprecated. Preserved for backward compatibility
879 std::string
880 Reader::getFormatedErrorMessages() const
881 {
882 return getFormattedErrorMessages();
883 }
884
885
886 std::string
887 Reader::getFormattedErrorMessages() const
888 {
889 std::string formattedMessage;
890 for ( Errors::const_iterator itError = errors_.begin();
891 itError != errors_.end();
892 ++itError )
893 {
894 const ErrorInfo &error = *itError;
895 formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n";
896 formattedMessage += " " + error.message_ + "\n";
897 if ( error.extra_ )
898 formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n";
899 }
900 return formattedMessage;
901 }
902
903
904 std::istream& operator>>( std::istream &sin, Value &root )
905 {
906 Json::Reader reader;
907 bool ok = reader.parse(sin, root, true);
908 if (!ok) {
909 fprintf(
910 stderr,
911 "Error from reader: %s",
912 reader.getFormattedErrorMessages().c_str());
913
914 JSON_FAIL_MESSAGE("reader error");
915 }
916 return sin;
917 }
918
919
920 } // namespace Json
OLDNEW
« no previous file with comments | « DEPS ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698