Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE.md file. | 3 // BSD-style license that can be found in the LICENSE.md file. |
| 4 | 4 |
| 5 import 'dart:io'; | 5 import 'dart:io'; |
| 6 import 'dart:typed_data'; | 6 import 'dart:typed_data'; |
| 7 | 7 |
| 8 import 'package:http/http.dart'; | 8 import 'package:http/http.dart'; |
| 9 | 9 |
| 10 int _min(int m, int n) => (m < n) ? m : n; | 10 int _min(int m, int n) => (m < n) ? m : n; |
| 11 | 11 |
| 12 dynamic getJson(String host, String resource) { | 12 dynamic getJson(String host, String resource) { |
| 13 var socket = new Socket.connect(host, 80); | 13 var socket = new Socket.connect(host, 80); |
| 14 HttpConnection connection = new HttpConnection(socket); | 14 HttpConnection connection = new HttpConnection(socket); |
| 15 HttpRequest request = new HttpRequest(resource); | 15 HttpRequest request = new HttpRequest(resource); |
| 16 request.headers["Host"] = host; | 16 request.headers["Host"] = host; |
| 17 HttpResponse response = connection.send(request); | 17 HttpResponse response = connection.send(request); |
| 18 return new JsonParser(new String.fromCharCodes(response.body)).parse(); | 18 return new JsonParser(new String.fromCharCodes(response.body)).parse(); |
| 19 } | 19 } |
| 20 | 20 |
| 21 class JsonParser { | 21 class JsonParser { |
| 22 int _index = 0; | 22 int _offset = 0; |
| 23 String _json; | 23 String _json; |
| 24 String _whitespaceUnits = ' \t\n'; | |
| 25 String _delimiterUnits = ' \t\n,[]{}'; | |
| 26 | 24 |
| 27 JsonParser(this._json); | 25 JsonParser(this._json); |
| 28 | 26 |
| 29 dynamic parse() { | 27 dynamic parse() { |
| 30 _whitespace(); | 28 _whitespace(); |
| 31 var result = _parseValue(); | 29 var result = _parseValue(); |
| 32 _whitespace(); | 30 _whitespace(); |
| 33 if (_hasCurrent) { | 31 if (_hasCurrent) { |
| 34 throw "Parse failed to consume: ${_json.substring(_index)}"; | 32 throw "Parse failed to consume: ${_json.substring(_offset)}"; |
| 35 } | 33 } |
| 36 return result; | 34 return result; |
| 37 } | 35 } |
| 38 | 36 |
| 39 int get _currentUnit => _json.codeUnitAt(_index); | 37 bool get _hasCurrent => _offset < _json.length; |
| 40 String get _current => _json[_index]; | 38 int get _current => _json.codeUnitAt(_offset); |
| 41 bool get _hasCurrent => _index < _json.length; | 39 String get _currentChar => _json[_offset]; |
| 40 void _consume() { ++_offset; } | |
| 42 | 41 |
| 43 bool _containsCurrent(String units) { | 42 void _whitespace() { |
| 44 int length = units.length; | 43 while (_hasCurrent && _isWhitespace(_current)) _consume(); |
| 45 int unit = _currentUnit; | 44 } |
| 46 for (int i = 0; i < length; ++i) { | 45 |
| 47 if (unit == units.codeUnitAt(i)) return true; | 46 bool _optional(int char) { |
| 47 if (_current == char) { | |
| 48 _consume(); | |
| 49 return true; | |
| 48 } | 50 } |
| 49 return false; | 51 return false; |
| 50 } | 52 } |
| 51 | 53 |
| 52 void _whitespace() { | 54 void _expect(int char) { |
| 53 while (_hasCurrent && _containsCurrent(_whitespaceUnits)) ++_index; | 55 if (!_optional(char)) { |
| 54 } | 56 String string = new String.fromCharCodes([char]); |
| 55 | 57 throw "Expected $string at index $_offset, found $_currentChar"; |
| 56 void _parseToken(String token) { | |
| 57 if (!_tryParseToken(token)) { | |
| 58 throw "Expected $token at index $_index, found $_current"; | |
| 59 } | 58 } |
| 60 } | 59 } |
| 61 | 60 |
| 62 bool _tryParseToken(String token) { | 61 bool _tryParseToken(String token) { |
| 63 int length = token.length; | 62 int length = token.length; |
| 64 int start = _index; | 63 int start = _offset; |
| 65 int end = start + length; | 64 int end = start + length; |
| 66 if (end > _json.length) return false; | 65 if (end > _json.length) return false; |
| 67 for (int i = 0; i < length; ++i) { | 66 for (int i = 0; i < length; ++i) { |
| 68 if (token.codeUnitAt(i) != _json.codeUnitAt(start + i)) { | 67 if (token.codeUnitAt(i) != _json.codeUnitAt(start + i)) { |
| 69 return false; | 68 return false; |
| 70 } | 69 } |
| 71 } | 70 } |
| 72 _index += length; | 71 _offset += length; |
| 73 return true; | 72 return true; |
| 74 } | 73 } |
| 75 | 74 |
| 76 bool get _isDelimiter => _containsCurrent(_delimiterUnits); | |
| 77 | |
| 78 String _readToken() { | 75 String _readToken() { |
| 79 int start = _index; | 76 int start = _offset; |
| 80 while (_hasCurrent && !_isDelimiter) ++_index; | 77 while (_hasCurrent && !_isSeparator(_current)) _consume(); |
| 81 return _json.substring(start, _index); | 78 return _json.substring(start, _offset); |
| 82 } | 79 } |
| 83 | 80 |
| 84 dynamic _parseValue() { | 81 dynamic _parseValue() { |
| 85 _whitespace(); | 82 _whitespace(); |
| 86 if (_tryParseToken('{')) return _parseMap(); | 83 if (_optional(_CHAR_BRACE_OPEN)) return _parseMap(); |
| 87 if (_tryParseToken('[')) return _parseList(); | 84 if (_optional(_CHAR_SQUARE_OPEN)) return _parseList(); |
| 88 if (_tryParseToken('"')) return _parseString(); | 85 if (_optional(_CHAR_DOUBLE_QUOTE)) return _parseString(); |
| 89 if (_tryParseToken('true')) return true; | 86 if (_tryParseToken(_TOKEN_TRUE)) return true; |
| 90 if (_tryParseToken('false')) return false; | 87 if (_tryParseToken(_TOKEN_FALSE)) return false; |
| 91 if (_tryParseToken('null')) return null; | 88 if (_tryParseToken(_TOKEN_NULL)) return null; |
| 92 | 89 |
| 93 int tryIndex = _index; | 90 int start = _offset; |
| 94 String token = _readToken(); | 91 String token = _readToken(); |
| 95 bool error = false; | 92 bool error = false; |
| 96 // TODO(zerny) floating point numbers. | 93 // TODO(zerny) floating point numbers. |
| 97 int tryInt = int.parse(token, onError: (_) { error = true; return 0; }); | 94 int tryInt = int.parse(token, onError: (_) { error = true; return 0; }); |
| 98 if (!error) return tryInt; | 95 if (!error) return tryInt; |
| 99 | 96 |
| 100 String context = _json.substring(tryIndex, _min(tryIndex + 10, _json.length) ); | 97 String context = _json.substring(start, _min(start + 10, _json.length)); |
| 101 throw "Parse failed at index: $_index (context: '$context...')"; | 98 throw "Parse failed at index: $_offset (context: '$context...')"; |
| 102 } | 99 } |
| 103 | 100 |
| 104 Map _parseMap() { | 101 Map _parseMap() { |
| 105 Map map = new Map(); | 102 Map map = new Map(); |
| 106 _whitespace(); | 103 _whitespace(); |
| 107 while (!_tryParseToken('}')) { | 104 while (!_optional(_CHAR_BRACE_CLOSE)) { |
| 108 var key = _parseValue(); | 105 var key = _parseValue(); |
| 109 _whitespace(); | 106 _whitespace(); |
| 110 _parseToken(':'); | 107 _expect(_CHAR_COLON); |
| 111 var value = _parseValue(); | 108 var value = _parseValue(); |
| 112 map[key] = value; | 109 map[key] = value; |
| 113 _whitespace(); | 110 _whitespace(); |
| 114 _tryParseToken(','); | 111 _optional(_CHAR_COMMA); |
| 115 _whitespace(); | 112 _whitespace(); |
| 116 } | 113 } |
| 117 return map; | 114 return map; |
| 118 } | 115 } |
| 119 | 116 |
| 120 List _parseList() { | 117 List _parseList() { |
| 121 List list = new List(); | 118 List list = new List(); |
| 122 _whitespace(); | 119 _whitespace(); |
| 123 while (!_tryParseToken(']')) { | 120 while (!_optional(_CHAR_SQUARE_CLOSE)) { |
| 124 list.add(_parseValue()); | 121 list.add(_parseValue()); |
| 125 _whitespace(); | 122 _whitespace(); |
| 126 _tryParseToken(','); | 123 _optional(_CHAR_COMMA); |
|
Anders Johnsen
2015/03/05 07:35:31
Would this allow
[1 2 3 4]
?
(ditto above)
zerny-google
2015/03/05 08:12:31
Yes. Fortunately it does parse well-formed json :-
| |
| 127 _whitespace(); | 124 _whitespace(); |
| 128 } | 125 } |
| 129 return list; | 126 return list; |
| 130 } | 127 } |
| 131 | 128 |
| 132 String _parseString() { | 129 String _parseString() { |
| 133 int start = _index; | 130 int start = _offset; |
| 134 while (_hasCurrent && _current != '"') ++_index; | 131 while (_hasCurrent && _current != _CHAR_DOUBLE_QUOTE) { |
| 135 return _json.substring(start, _index++); | 132 if (_current == _CHAR_BACKSLASH) _consume(); |
| 133 _consume(); | |
| 134 } | |
| 135 _expect(_CHAR_DOUBLE_QUOTE); | |
| 136 return _json.substring(start, _offset - 1); | |
| 136 } | 137 } |
| 138 | |
| 139 static bool _isWhitespace(int char) => | |
| 140 char == _CHAR_TAB || | |
| 141 char == _CHAR_LINE_FEED || | |
| 142 char == _CHAR_SPACE; | |
| 143 | |
| 144 static bool _isSeparator(int char) => | |
| 145 _isWhitespace(char) || | |
| 146 char == _CHAR_PAREN_OPEN || | |
| 147 char == _CHAR_PAREN_CLOSE || | |
| 148 char == _CHAR_COMMA || | |
| 149 char == _CHAR_COLON || | |
| 150 char == _CHAR_SQUARE_OPEN || | |
| 151 char == _CHAR_SQUARE_CLOSE || | |
| 152 char == _CHAR_BRACE_OPEN || | |
| 153 char == _CHAR_BRACE_CLOSE; | |
| 154 | |
| 155 static const String _TOKEN_TRUE = 'true'; | |
| 156 static const String _TOKEN_FALSE = 'false'; | |
| 157 static const String _TOKEN_NULL = 'null'; | |
| 158 | |
| 159 static const int _CHAR_TAB = 9; | |
| 160 static const int _CHAR_LINE_FEED = 10; | |
| 161 static const int _CHAR_SPACE = 32; | |
| 162 static const int _CHAR_DOUBLE_QUOTE = 34; | |
| 163 static const int _CHAR_PAREN_OPEN = 40; | |
| 164 static const int _CHAR_PAREN_CLOSE = 41; | |
| 165 static const int _CHAR_COMMA = 44; | |
| 166 static const int _CHAR_COLON = 58; | |
| 167 static const int _CHAR_SQUARE_OPEN = 91; | |
| 168 static const int _CHAR_BACKSLASH = 92; | |
| 169 static const int _CHAR_SQUARE_CLOSE = 93; | |
| 170 static const int _CHAR_BRACE_OPEN = 123; | |
| 171 static const int _CHAR_BRACE_CLOSE = 125; | |
| 137 } | 172 } |
| OLD | NEW |