A toolkit for working with phylogenetic data.
v0.19.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
utils/formats/json/reader.cpp
Go to the documentation of this file.
1 /*
2  Genesis - A toolkit for working with phylogenetic data.
3  Copyright (C) 2014-2017 Lucas Czech
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 
18  Contact:
19  Lucas Czech <lucas.czech@h-its.org>
20  Exelixis Lab, Heidelberg Institute for Theoretical Studies
21  Schloss-Wolfsbrunnenweg 35, D-69118 Heidelberg, Germany
22 */
23 
32 
33 #include <cassert>
34 #include <cctype>
35 #include <fstream>
36 #include <sstream>
37 #include <stdexcept>
38 
47 
48 namespace genesis {
49 namespace utils {
50 
51 // =================================================================================================
52 // Reading
53 // =================================================================================================
54 
55 JsonDocument JsonReader::from_stream( std::istream& input_stream ) const
56 {
57  utils::InputStream is( utils::make_unique< utils::StreamInputSource >( input_stream ));
58  return parse( is );
59 }
60 
61 JsonDocument JsonReader::from_file (const std::string& filename ) const
62 {
63  utils::InputStream is( utils::make_unique< utils::FileInputSource >( filename ));
64  return parse( is );
65 }
66 
67 JsonDocument JsonReader::from_string (const std::string& json ) const
68 {
69  utils::InputStream is( utils::make_unique< utils::StringInputSource >( json ));
70  return parse( is );
71 }
72 
73 // =================================================================================================
74 // Parsing
75 // =================================================================================================
76 
77 // -----------------------------------------------------------------------------
78 // Parse
79 // -----------------------------------------------------------------------------
80 
82 {
83  JsonDocument result = parse_value( input_stream );
84  skip_while( input_stream, isspace );
85  if( input_stream ) {
86  throw std::runtime_error(
87  "Expected end of input while reading Json at " + input_stream.at()
88  );
89  }
90  return result;
91 }
92 
93 // -----------------------------------------------------------------------------
94 // Parse Value
95 // -----------------------------------------------------------------------------
96 
98 {
99  auto& it = input_stream;
100 
101  // Go to first non-white char.
102  skip_while( it, isspace );
103  // while( it && lookup_[ *it ] == CharTypes::kSpace ) {
104  // ++it;
105  // }
106  // skip_while( it, [&]( char c ){ return lookup_[c] == CharTypes::kSpace; } );
107 
108  // If there is no content, return an empty Json doc.
109  if( !it ) {
110  return nullptr;
111 
112  // Parse an array.
113  } else if( *it == '[' ) {
114  return parse_array( it );
115 
116  // Parse an object.
117  } else if( *it == '{' ) {
118  return parse_object( it );
119 
120  // Parse a string.
121  } else if( *it == '"' ) {
122  return parse_quoted_string( it );
123 
124  // Either null or boolean.
125  } else if( isalpha( *it ) ) {
126  auto value = to_lower( read_while( it, isalpha ));
127 
128  // If it is a null token, return an empty Json doc.
129  if( value == "null" ) {
130  return nullptr;
131 
132  // Otherwise return a boolean value.
133  } else if( value == "true" ) {
134  return JsonDocument::boolean( true );
135  } else if( value == "false" ) {
136  return JsonDocument::boolean( false );
137  } else {
138  throw std::runtime_error(
139  "Unexpected Json input string: '" + value + "' at " + it.at() + "."
140  );
141  }
142 
143  // Parse a number.
144  } else if( isdigit( *it ) or char_is_sign( *it ) or *it == '.' ) {
145  return parse_number( it );
146 
147  // Parse error.
148  } else {
149  throw std::runtime_error(
150  "Unexpected Json input char: '" + std::string( 1, *it ) + "' at " + it.at() + "."
151  );
152  }
153 }
154 
155 // -----------------------------------------------------------------------------
156 // Parse Array
157 // -----------------------------------------------------------------------------
158 
160 {
162  auto& it = input_stream;
163 
164  // Initial check whether this actually is an array.
165  skip_while( it, isspace );
166  read_char_or_throw( it, '[' );
167 
168  // Check for empty array.
169  skip_while( it, isspace );
170  if( it && *it == ']' ) {
171  assert( it && *it == ']' );
172  ++it;
173  return doc;
174  }
175 
176  while( it ) {
177  // Get the element.
178  doc.emplace_back( parse_value( it ));
179 
180  // Check for end of array, leave if found.
181  skip_while( it, isspace );
182  if( !it || *it == ']' ) {
183  break;
184  }
185 
186  // We expect more. Or throw, if we unexpectedly are at the end or have an illegal char.
187  read_char_or_throw( it, ',' );
188  skip_while( it, isspace );
189  }
190 
191  // We are at the end of the array. Move to next char.
192  if( !it || *it != ']' ) {
193  throw std::runtime_error( "Unexpected end of Json array at " + it.at() );
194  }
195  assert( it && *it == ']' );
196  ++it;
197 
198  return doc;
199 }
200 
201 // -----------------------------------------------------------------------------
202 // Parse Object
203 // -----------------------------------------------------------------------------
204 
206 {
208  auto& it = input_stream;
209 
210  // Initial check whether this actually is an object.
211  skip_while( it, isspace );
212  read_char_or_throw( it, '{' );
213 
214  // Check for empty object.
215  skip_while( it, isspace );
216  if( it && *it == '}' ) {
217  assert( it && *it == '}' );
218  ++it;
219  return doc;
220  }
221 
222  while( it ) {
223  // Get the key.
224  affirm_char_or_throw( it, '"' );
225  auto key = parse_quoted_string( it );
226 
227  // Find the colon and skip it.
228  skip_while( it, isspace );
229  read_char_or_throw( it, ':' );
230 
231  // Get the value and insert into object.
232  skip_while( it, isspace );
233  doc[ key ] = parse_value( it );
234 
235  // Check for end of object, leave if found.
236  skip_while( it, isspace );
237  if( !it || *it == '}' ) {
238  break;
239  }
240 
241  // We expect more. Or throw, if we unexpectedly are at the end or have an illegal char.
242  read_char_or_throw( it, ',' );
243  skip_while( it, isspace );
244  }
245 
246  // We are at the end of the object. Move to next char.
247  if( !it || *it != '}' ) {
248  throw std::runtime_error( "Unexpected end of Json object at " + it.at() );
249  }
250  assert( it && *it == '}' );
251  ++it;
252 
253  return doc;
254 }
255 
256 // -----------------------------------------------------------------------------
257 // Parse Number
258 // -----------------------------------------------------------------------------
259 
261 {
262  auto& it = input_stream;
263  skip_while( it, isspace );
264  if( !it ) {
265  throw std::runtime_error(
266  "Expecting number in " + it.source_name() + " at " + it.at() + "."
267  );
268  }
269 
270  // Sign
271  bool is_neg = false;
272  if( *it == '-' ){
273  is_neg = true;
274  ++it;
275  } else if( *it == '+' ) {
276  ++it;
277  }
278 
279  // Integer Part
280  bool found_mantisse = false;
282  while( it && isdigit( *it )) {
283  int y = *it - '0';
284  ix *= 10;
285  ix += y;
286  ++it;
287  found_mantisse = true;
288  }
289 
290  // If not float
291  if( found_mantisse && !( *it == '.' || tolower(*it) == 'e' ) ) {
292  if( is_neg ) {
293  return JsonDocument::number_signed( -ix );
294  } else {
295  return JsonDocument::number_unsigned( ix );
296  }
297  }
298 
299  // Decimal part
301  if( it && *it == '.' ) {
302  ++it;
303 
305  while( it && isdigit( *it )) {
306  pos /= 10.0;
307  int y = *it - '0';
308  dx += y * pos;
309  ++it;
310  found_mantisse = true;
311  }
312  }
313 
314  // We need to have some digits before the exponential part.
315  if( ! found_mantisse ) {
316  throw std::runtime_error(
317  "Invalid float number in " + it.source_name() + " at " + it.at() + "."
318  );
319  }
320 
321  // Exponential part
322  if( it && tolower(*it) == 'e' ) {
323  ++it;
324 
325  // Read the exp. If there are no digits, this throws.
326  int e = parse_signed_integer<int>( it );
327  if( e != 0 ) {
329  if( e < 0 ) {
330  base = 0.1;
331  e = -e;
332  } else {
333  base = 10;
334  }
335 
336  while( e != 1 ) {
337  if( ( e & 1 ) == 0 ) {
338  base = base * base;
339  e >>= 1;
340  } else {
341  dx *= base;
342  --e;
343  }
344  }
345  dx *= base;
346  }
347  }
348 
349  // Sign
350  if (is_neg) {
351  dx = -dx;
352  }
353 
354  return JsonDocument::number_float( dx );
355 }
356 
357 } // namespace utils
358 } // namespace genesis
std::string parse_quoted_string(utils::InputStream &source, bool use_escapes, bool use_twin_quotes, bool include_qmarks)
Read a string in quotation marks from a stream and return it.
Definition: parser.cpp:116
void skip_while(InputStream &source, char criterion)
Lexing function that advances the stream while its current char equals the provided one...
Definition: scanner.hpp:151
JsonDocument from_file(const std::string &filename) const
Take a JSON document file path and parse its contents into a JsonDocument.
static JsonDocument number_signed(NumberSignedType value)
Explicitly create a signed number.
static JsonDocument array(std::initializer_list< JsonDocument > init=std::initializer_list< JsonDocument >())
Explicitly create an array from an initializer list.
JsonDocument parse_array(InputStream &input_stream) const
std::string at() const
Return a textual representation of the current input position in the form "line:column".
std::string to_lower(std::string const &str)
Return an all-lowercase copy of the given string, locale-aware.
Definition: string.hpp:198
JsonDocument parse_value(InputStream &input_stream) const
Provides some valuable additions to STD.
static JsonDocument object(std::initializer_list< JsonDocument > init=std::initializer_list< JsonDocument >())
Explicitly create an object from an initializer list.
JsonDocument parse_number(InputStream &input_stream) const
std::string read_while(InputStream &source, char criterion)
Lexing function that reads from the stream while its current char equals the provided one...
Definition: scanner.hpp:214
JsonDocument from_string(const std::string &json) const
Take a string containing a JSON document and parse its contents into a JsonDocument.
void affirm_char_or_throw(InputStream &source, char criterion, SkipWhitespace skip_ws=SkipWhitespace::kNone)
Lexing function that checks whether the current char from the stream equals the provided one...
Definition: scanner.hpp:380
static JsonDocument boolean(BooleanType value)
Explicitly create a boolean.
char read_char_or_throw(InputStream &source, char criterion, SkipWhitespace skip_ws=SkipWhitespace::kNone)
Lexing function that reads a single char from the stream and checks whether it equals the provided on...
Definition: scanner.hpp:297
Provides some commonly used string utility functions.
Provides functions for accessing the file system.
Store a Json value of any kind.
static JsonDocument number_unsigned(NumberUnsignedType value)
Explicitly create an unsigned number.
JsonDocument parse_object(InputStream &input_stream) const
JsonDocument parse(InputStream &input_stream) const
void emplace_back(Args &&...args)
JsonDocument from_stream(std::istream &input_stream) const
Read from a stream containing a JSON document and parse its contents into a JsonDocument.
static JsonDocument number_float(NumberFloatType value)
Explicitly create a float number.
Stream interface for reading data from an InputSource, that keeps track of line and column counters...
bool char_is_sign(const char c)
Return whether a char is a sign (+-).
Definition: char.hpp:57