A library for working with phylogenetic and population genetic data.
v0.32.0
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-2022 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 <lczech@carnegiescience.edu>
20  Department of Plant Biology, Carnegie Institution For Science
21  260 Panama Street, Stanford, CA 94305, USA
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::read( std::shared_ptr<utils::BaseInputSource> source ) const
56 {
57  utils::InputStream is( source );
58  return parse( is );
59 }
60 
61 // =================================================================================================
62 // Parsing
63 // =================================================================================================
64 
65 // -----------------------------------------------------------------------------
66 // Parse
67 // -----------------------------------------------------------------------------
68 
70 {
71  JsonDocument result = parse_value( input_stream );
72  skip_while( input_stream, is_space );
73  if( input_stream ) {
74  throw std::runtime_error(
75  "Expected end of input while reading Json at " + input_stream.at()
76  );
77  }
78  return result;
79 }
80 
81 // -----------------------------------------------------------------------------
82 // Parse Value
83 // -----------------------------------------------------------------------------
84 
86 {
87  auto& it = input_stream;
88 
89  // Go to first non-white char.
90  skip_while( it, is_space );
91  // while( it && lookup_[ *it ] == CharTypes::kSpace ) {
92  // ++it;
93  // }
94  // skip_while( it, [&]( char c ){ return lookup_[c] == CharTypes::kSpace; } );
95 
96  // If there is no content, return an empty Json doc.
97  if( !it ) {
98  return nullptr;
99 
100  // Parse an array.
101  } else if( *it == '[' ) {
102  return parse_array( it );
103 
104  // Parse an object.
105  } else if( *it == '{' ) {
106  return parse_object( it );
107 
108  // Parse a string.
109  } else if( *it == '"' ) {
110  return parse_quoted_string( it );
111 
112  // Either null or boolean.
113  } else if( is_alpha( *it ) ) {
114  auto value = utils::to_lower( read_while( it, is_alpha ));
115 
116  // If it is a null token, return an empty Json doc.
117  if( value == "null" ) {
118  return nullptr;
119 
120  // Otherwise return a boolean value.
121  } else if( value == "true" ) {
122  return JsonDocument::boolean( true );
123  } else if( value == "false" ) {
124  return JsonDocument::boolean( false );
125  } else {
126  throw std::runtime_error(
127  "Unexpected Json input string: '" + value + "' at " + it.at() + "."
128  );
129  }
130 
131  // Parse a number.
132  } else if( is_digit( *it ) or ( *it == '+' ) or ( *it == '-' ) or ( *it == '.' )) {
133  return parse_number( it );
134 
135  // Parse error.
136  } else {
137  throw std::runtime_error(
138  "Unexpected Json input char: '" + std::string( 1, *it ) + "' at " + it.at() + "."
139  );
140  }
141 }
142 
143 // -----------------------------------------------------------------------------
144 // Parse Array
145 // -----------------------------------------------------------------------------
146 
148 {
150  auto& it = input_stream;
151 
152  // Initial check whether this actually is an array.
153  skip_while( it, is_space );
154  read_char_or_throw( it, '[' );
155 
156  // Check for empty array.
157  skip_while( it, is_space );
158  if( it && *it == ']' ) {
159  assert( it && *it == ']' );
160  ++it;
161  return doc;
162  }
163 
164  while( it ) {
165  // Get the element.
166  doc.emplace_back( parse_value( it ));
167 
168  // Check for end of array, leave if found.
169  skip_while( it, is_space );
170  if( !it || *it == ']' ) {
171  break;
172  }
173 
174  // We expect more. Or throw, if we unexpectedly are at the end or have an illegal char.
175  read_char_or_throw( it, ',' );
176  skip_while( it, is_space );
177  }
178 
179  // We are at the end of the array. Move to next char.
180  if( !it || *it != ']' ) {
181  throw std::runtime_error( "Unexpected end of Json array at " + it.at() );
182  }
183  assert( it && *it == ']' );
184  ++it;
185 
186  return doc;
187 }
188 
189 // -----------------------------------------------------------------------------
190 // Parse Object
191 // -----------------------------------------------------------------------------
192 
194 {
196  auto& it = input_stream;
197 
198  // Initial check whether this actually is an object.
199  skip_while( it, is_space );
200  read_char_or_throw( it, '{' );
201 
202  // Check for empty object.
203  skip_while( it, is_space );
204  if( it && *it == '}' ) {
205  assert( it && *it == '}' );
206  ++it;
207  return doc;
208  }
209 
210  while( it ) {
211  // Get the key.
212  affirm_char_or_throw( it, '"' );
213  auto key = parse_quoted_string( it );
214 
215  // Find the colon and skip it.
216  skip_while( it, is_space );
217  read_char_or_throw( it, ':' );
218 
219  // Get the value and insert into object.
220  skip_while( it, is_space );
221  doc[ key ] = parse_value( it );
222 
223  // Check for end of object, leave if found.
224  skip_while( it, is_space );
225  if( !it || *it == '}' ) {
226  break;
227  }
228 
229  // We expect more. Or throw, if we unexpectedly are at the end or have an illegal char.
230  read_char_or_throw( it, ',' );
231  skip_while( it, is_space );
232  }
233 
234  // We are at the end of the object. Move to next char.
235  if( !it || *it != '}' ) {
236  throw std::runtime_error( "Unexpected end of Json object at " + it.at() );
237  }
238  assert( it && *it == '}' );
239  ++it;
240 
241  return doc;
242 }
243 
244 // -----------------------------------------------------------------------------
245 // Parse Number
246 // -----------------------------------------------------------------------------
247 
249 {
250  auto& it = input_stream;
251  skip_while( it, is_space );
252  if( !it ) {
253  throw std::runtime_error(
254  "Expecting number in " + it.source_name() + " at " + it.at() + "."
255  );
256  }
257 
258  // Sign
259  bool is_neg = false;
260  if( *it == '-' ){
261  is_neg = true;
262  ++it;
263  } else if( *it == '+' ) {
264  ++it;
265  }
266 
267  // Integer Part
268  bool found_mantisse = false;
270  while( it && is_digit( *it )) {
271  int y = *it - '0';
272  ix *= 10;
273  ix += y;
274  ++it;
275  found_mantisse = true;
276  }
277 
278  // If not float
279  if( found_mantisse && !( *it == '.' || utils::to_lower(*it) == 'e' ) ) {
280  if( is_neg ) {
281  return JsonDocument::number_signed( -ix );
282  } else {
283  return JsonDocument::number_unsigned( ix );
284  }
285  }
286 
287  // Decimal part
289  if( it && *it == '.' ) {
290  ++it;
291 
293  while( it && is_digit( *it )) {
294  pos /= 10.0;
295  int y = *it - '0';
296  dx += y * pos;
297  ++it;
298  found_mantisse = true;
299  }
300  }
301 
302  // We need to have some digits before the exponential part.
303  if( ! found_mantisse ) {
304  throw std::runtime_error(
305  "Invalid float number in " + it.source_name() + " at " + it.at() + "."
306  );
307  }
308 
309  // Exponential part
310  if( it && utils::to_lower(*it) == 'e' ) {
311  ++it;
312 
313  // Read the exp. If there are no digits, this throws.
314  int e = parse_signed_integer<int>( it );
315  if( e != 0 ) {
317  if( e < 0 ) {
318  base = 0.1;
319  e = -e;
320  } else {
321  base = 10;
322  }
323 
324  while( e != 1 ) {
325  if( ( e & 1 ) == 0 ) {
326  base = base * base;
327  e >>= 1;
328  } else {
329  dx *= base;
330  --e;
331  }
332  }
333  dx *= base;
334  }
335  }
336 
337  // Sign
338  if (is_neg) {
339  dx = -dx;
340  }
341 
342  return JsonDocument::number_float( dx );
343 }
344 
345 } // namespace utils
346 } // namespace genesis
genesis::utils::InputStream::at
std::string at() const
Return a textual representation of the current input position in the form "line:column".
Definition: input_stream.hpp:437
genesis::utils::InputStream
Stream interface for reading data from an InputSource, that keeps track of line and column counters.
Definition: input_stream.hpp:88
parser.hpp
genesis::utils::JsonReader::parse_object
JsonDocument parse_object(InputStream &input_stream) const
Definition: utils/formats/json/reader.cpp:193
genesis::utils::JsonDocument::boolean
static JsonDocument boolean(BooleanType value)
Explicitly create a boolean.
Definition: json/document.hpp:535
genesis::utils::read_while
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:216
fs.hpp
Provides functions for accessing the file system.
genesis::utils::JsonDocument
Store a Json value of any kind.
Definition: json/document.hpp:114
genesis::utils::JsonDocument::number_unsigned
static JsonDocument number_unsigned(NumberUnsignedType value)
Explicitly create an unsigned number.
Definition: json/document.hpp:568
std.hpp
Provides some valuable additions to STD.
genesis::utils::affirm_char_or_throw
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:385
string.hpp
Provides some commonly used string utility functions.
input_stream.hpp
genesis::utils::is_space
constexpr bool is_space(char c) noexcept
Return whether a char is some form of white space charater, so either space, tab, new line,...
Definition: char.hpp:200
genesis::utils::JsonDocument::emplace_back
void emplace_back(Args &&... args)
Definition: json/document.hpp:865
genesis::utils::read_char_or_throw
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:299
genesis::utils::JsonDocument::NumberFloatType
double NumberFloatType
Definition: json/document.hpp:147
genesis::utils::JsonDocument::number_float
static JsonDocument number_float(NumberFloatType value)
Explicitly create a float number.
Definition: json/document.hpp:546
document.hpp
genesis::utils::JsonReader::parse_number
JsonDocument parse_number(InputStream &input_stream) const
Definition: utils/formats/json/reader.cpp:248
genesis::utils::skip_while
void skip_while(InputStream &source, char criterion)
Lexing function that advances the stream while its current char equals the provided one.
Definition: scanner.hpp:153
reader.hpp
genesis::utils::JsonReader::parse
JsonDocument parse(InputStream &input_stream) const
Definition: utils/formats/json/reader.cpp:69
genesis::utils::JsonDocument::array
static JsonDocument array(std::initializer_list< JsonDocument > init=std::initializer_list< JsonDocument >())
Explicitly create an array from an initializer list.
Definition: json/document.hpp:508
genesis
Container namespace for all symbols of genesis in order to keep them separate when used as a library.
Definition: placement/formats/edge_color.cpp:42
genesis::utils::is_digit
constexpr bool is_digit(char c) noexcept
Return whether a char is a digit (0-9), ASCII-only.
Definition: char.hpp:95
char.hpp
genesis::utils::JsonDocument::NumberUnsignedType
std::uint64_t NumberUnsignedType
Definition: json/document.hpp:149
genesis::utils::JsonDocument::number_signed
static JsonDocument number_signed(NumberSignedType value)
Explicitly create a signed number.
Definition: json/document.hpp:557
genesis::utils::JsonReader::read
JsonDocument read(std::shared_ptr< BaseInputSource > source) const
Read from a source containing a JSON document and parse its contents into a JsonDocument.
Definition: utils/formats/json/reader.cpp:55
scanner.hpp
genesis::utils::to_lower
constexpr char to_lower(char c) noexcept
Return the lower case version of a letter, ASCII-only.
Definition: char.hpp:221
genesis::utils::parse_quoted_string
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:547
genesis::utils::JsonDocument::object
static JsonDocument object(std::initializer_list< JsonDocument > init=std::initializer_list< JsonDocument >())
Explicitly create an object from an initializer list.
Definition: json/document.hpp:517
genesis::utils::JsonReader::parse_array
JsonDocument parse_array(InputStream &input_stream) const
Definition: utils/formats/json/reader.cpp:147
genesis::utils::JsonReader::parse_value
JsonDocument parse_value(InputStream &input_stream) const
Definition: utils/formats/json/reader.cpp:85
genesis::utils::is_alpha
constexpr bool is_alpha(char c) noexcept
Return whether a char is a letter (a-z or A-Z), ASCII-only.
Definition: char.hpp:135