Ticket #7726: config_file_iterator.cpp

File config_file_iterator.cpp, 24.5 KB (added by p.brockamp@…, 10 years ago)

Test

Line 
1// (C) Copyright Gennadiy Rozental 2005-2008.
2// Use, modification, and distribution are subject to the
3// Boost Software License, Version 1.0. (See accompanying file
4// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5
6// See http://www.boost.org/libs/test for the library home page.
7//
8// File : $RCSfile$
9//
10// Version : $Revision: 54633 $
11//
12// Description : flexible configuration file iterator implementation
13// ***************************************************************************
14
15// Boost.Runtime.Parameter
16#include <boost/test/utils/runtime/config.hpp>
17
18#include <boost/test/utils/runtime/file/config_file_iterator.hpp>
19#include <boost/test/utils/runtime/validation.hpp>
20
21#ifndef UNDER_CE
22#include <boost/test/utils/runtime/env/environment.hpp>
23#endif
24
25
26// Boost
27#include <boost/utility.hpp>
28#include <boost/scoped_array.hpp>
29#include <boost/bind.hpp>
30
31// Boost.Test
32#include <boost/test/utils/basic_cstring/compare.hpp>
33#include <boost/test/utils/algorithm.hpp>
34#include <boost/test/utils/iterator/token_iterator.hpp>
35#include <boost/test/utils/assign_op.hpp>
36
37// STL
38#include <memory>
39#include <map>
40#include <list>
41#include <vector>
42#include <fstream>
43#include <cctype>
44#include <iostream>
45
46namespace boost {
47
48namespace BOOST_RT_PARAM_NAMESPACE {
49
50namespace file {
51
52// ************************************************************************** //
53// ************** symbol_to_value_map ************** //
54// ************************************************************************** //
55
56template<typename ValueType>
57struct symbol_to_value_map : std::map<cstring, ValueType> {
58 template<typename ParamType>
59 void add( cstring name, ParamType const& value )
60 {
61 using namespace unit_test;
62
63 m_name_store.push_back( dstring() );
64
65 assign_op( m_name_store.back(), name, 0 );
66 assign_op( (*this)[m_name_store.back()], value, 0 );
67 }
68 void remove( cstring name )
69 {
70 std::list<dstring>::iterator it = std::find( m_name_store.begin(), m_name_store.end(), name );
71
72 m_name_store.erase( it );
73
74 std::map<cstring, ValueType>::erase( name );
75 }
76
77private:
78 std::list<dstring> m_name_store;
79};
80
81// ************************************************************************** //
82// ************** symbol_table_t ************** //
83// ************************************************************************** //
84
85typedef symbol_to_value_map<dstring> symbol_table_t;
86
87// ************************************************************************** //
88// ************** command_handler_map ************** //
89// ************************************************************************** //
90
91typedef symbol_to_value_map<config_file_iterator::command_handler> command_handler_map;
92
93// ************************************************************************** //
94// ************** is_valid_identifier ************** //
95// ************************************************************************** //
96
97static bool
98is_valid_identifier( cstring const& source )
99{
100 if( source.is_empty() )
101 return false;
102
103 cstring::const_iterator it = source.begin();
104
105 if( !std::isalpha( *it ) )
106 return false;
107
108 while( ++it < source.end() ) {
109 if( !std::isalnum( *it ) && *it != BOOST_RT_PARAM_LITERAL( '_' ) && *it != BOOST_RT_PARAM_LITERAL( '-' ) )
110 return false;
111 }
112
113 return true;
114}
115
116// ************************************************************************** //
117// ************** include_level ************** //
118// ************************************************************************** //
119
120struct include_level;
121typedef std::auto_ptr<include_level> include_level_ptr;
122
123struct include_level : noncopyable
124{
125 // Constructor
126 explicit include_level( cstring file_name, cstring path_separators, include_level* parent = 0 );
127
128 // Data members
129 std::ifstream m_stream;
130 location m_curr_location;
131 include_level_ptr m_parent;
132};
133
134//____________________________________________________________________________//
135
136include_level::include_level( cstring file_name, cstring path_separators, include_level* parent_ )
137: m_parent( parent_ )
138{
139 if( file_name.is_empty() )
140 return;
141
142 assign_op( m_curr_location.first, file_name, 0 );
143 m_curr_location.second = 0;
144
145 m_stream.open( m_curr_location.first.c_str() );
146
147 if( !m_stream.is_open() && !!m_parent.get() ) {
148 cstring parent_path = m_parent->m_curr_location.first;
149 cstring::iterator it = unit_test::find_last_of( parent_path.begin(), parent_path.end(),
150 path_separators.begin(),
151 path_separators.end() );
152
153 if( it != parent_path.end() ) {
154 assign_op( m_curr_location.first, cstring( parent_path.begin(), it+1 ), 0 );
155 m_curr_location.first.append( file_name.begin(), file_name.end() );
156 m_stream.clear();
157 m_stream.open( m_curr_location.first.c_str() );
158 }
159 }
160
161 BOOST_RT_PARAM_VALIDATE_LOGIC( m_stream.is_open(), BOOST_RT_PARAM_LITERAL( "can't open file " ) << file_name );
162}
163
164//____________________________________________________________________________//
165
166// ************************************************************************** //
167// ************** config_file_iterator::Impl ************** //
168// ************************************************************************** //
169
170struct config_file_iterator::Impl : noncopyable {
171 // Constructor
172 Impl();
173
174 bool get_next_line( cstring& next_line );
175
176 void process_command_line( cstring line );
177 void process_include( cstring line );
178 void process_define( cstring line );
179 void process_undef( cstring line );
180 void process_ifdef( cstring line );
181 void process_ifndef( cstring line );
182 void process_else( cstring line );
183 void process_endif( cstring line );
184
185 boost::optional<cstring>
186 get_macro_value( cstring macro_name, bool ignore_missing = true );
187 void substitute_macros( cstring& where );
188
189 bool is_active_line() { return m_inactive_ifdef_level == 0; }
190
191 static bool match_front( cstring str, cstring pattern )
192 {
193 return str.size() >= pattern.size() && str.substr( 0, pattern.size() ) == pattern;
194 }
195 static bool match_back( cstring str, cstring pattern )
196 {
197 return str.size() >= pattern.size() && str.substr( str.size() - pattern.size() ) == pattern;
198 }
199
200 // Configurable parameters
201 dstring m_path_separators;
202 char_type m_line_delimeter;
203 dstring m_sl_comment_delimeter;
204 dstring m_command_delimeter;
205 dstring m_line_beak;
206 dstring m_macro_ref_begin;
207 dstring m_macro_ref_end;
208
209 dstring m_include_kw;
210 dstring m_define_kw;
211 dstring m_undef_kw;
212 dstring m_ifdef_kw;
213 dstring m_ifndef_kw;
214 dstring m_else_kw;
215 dstring m_endif_kw;
216
217 std::size_t m_buffer_size;
218
219 bool m_trim_trailing_spaces;
220 bool m_trim_leading_spaces;
221 bool m_skip_empty_lines;
222 bool m_detect_missing_macro;
223
224 // Data members
225 dstring m_post_subst_line;
226 scoped_array<char> m_buffer;
227 include_level_ptr m_curr_level;
228 symbol_table_t m_symbols_table;
229 std::vector<bool> m_conditional_states;
230 std::size_t m_inactive_ifdef_level;
231 command_handler_map m_command_handler_map;
232};
233
234//____________________________________________________________________________//
235
236config_file_iterator::Impl::Impl()
237: m_path_separators( BOOST_RT_PARAM_LITERAL( "/\\" ) )
238, m_line_delimeter( BOOST_RT_PARAM_LITERAL( '\n' ) )
239, m_sl_comment_delimeter( BOOST_RT_PARAM_LITERAL( "#" ) )
240, m_command_delimeter( BOOST_RT_PARAM_LITERAL( "$" ) )
241, m_line_beak( BOOST_RT_PARAM_LITERAL( "\\" ) )
242, m_macro_ref_begin( BOOST_RT_PARAM_LITERAL( "$" ) )
243, m_macro_ref_end( BOOST_RT_PARAM_LITERAL( "$" ) )
244
245, m_include_kw( BOOST_RT_PARAM_LITERAL( "include" ) )
246, m_define_kw( BOOST_RT_PARAM_LITERAL( "define" ) )
247, m_undef_kw( BOOST_RT_PARAM_LITERAL( "undef" ) )
248, m_ifdef_kw( BOOST_RT_PARAM_LITERAL( "ifdef" ) )
249, m_ifndef_kw( BOOST_RT_PARAM_LITERAL( "ifndef" ) )
250, m_else_kw( BOOST_RT_PARAM_LITERAL( "else" ) )
251, m_endif_kw( BOOST_RT_PARAM_LITERAL( "endif" ) )
252
253, m_buffer_size( 8192 )
254
255, m_trim_trailing_spaces( true )
256, m_trim_leading_spaces( false )
257, m_skip_empty_lines( true )
258, m_detect_missing_macro( true )
259
260, m_inactive_ifdef_level( 0 )
261{}
262
263//____________________________________________________________________________//
264
265bool
266config_file_iterator::Impl::get_next_line( cstring& line )
267{
268 bool broken_line = false;
269
270 line.clear();
271
272 while( !m_curr_level->m_stream.eof() || !!m_curr_level->m_parent.get() ) {
273 // 10. Switch to upper include level if current one is finished
274 // 20. Read/append next file line
275 // 30. Increment line number
276 // 40. Remove comments
277 // 50. Remove trailing and leading spaces
278 // 60. Skip empty string
279 // 70. Concatenate broken lines if needed. Put the result into line
280 // 80. If line is not completed, try to finish it by reading the next line
281 // 90. Process command line
282 // 100. Substitute macros references with their definitions
283 // 110. Next line found.
284
285 if( m_curr_level->m_stream.eof() ) { // 10 //
286 m_curr_level = m_curr_level->m_parent;
287 continue;
288 }
289
290 std::ifstream& input = m_curr_level->m_stream;
291 char_type* buffer_insert_pos = broken_line ? m_buffer.get() + line.size() : m_buffer.get();
292
293 input.getline( buffer_insert_pos, (std::streamsize)(m_buffer_size - line.size()), // 20 //
294 m_line_delimeter );
295
296 cstring next_line( buffer_insert_pos,
297 input.gcount() > 0
298 ? buffer_insert_pos + (input.eof() ? input.gcount() : (input.gcount()-1))
299 : buffer_insert_pos );
300
301
302 m_curr_level->m_curr_location.second++; // 30 //
303
304 cstring::size_type comment_pos = next_line.find( m_sl_comment_delimeter );
305 if( comment_pos != cstring::npos )
306 next_line.trim_right( next_line.begin()+comment_pos ); // 40 //
307
308 if( m_trim_trailing_spaces ) // 50 //
309 next_line.trim_right();
310 if( m_trim_leading_spaces && !broken_line )
311 next_line.trim_left();
312
313 if( next_line.is_empty() ) { // 60 //
314 if( m_skip_empty_lines )
315 continue;
316 else
317 next_line.assign( buffer_insert_pos, buffer_insert_pos );
318 }
319
320 line = broken_line ? cstring( line.begin(), next_line.end() ) : next_line; // 70 //
321
322 broken_line = match_back( line, m_line_beak );
323 if( broken_line ) { // 80 //
324 line.trim_right( 1 );
325 continue;
326 }
327
328 if( match_front( line, m_command_delimeter ) ) { // 90 //
329 process_command_line( line );
330 continue;
331 }
332
333 if( !is_active_line() )
334 continue;
335
336 substitute_macros( line ); // 100 //
337
338 return true; // 110 //
339 }
340
341 BOOST_RT_PARAM_VALIDATE_LOGIC( !broken_line, BOOST_RT_PARAM_LITERAL( "broken line is not completed" ) );
342 BOOST_RT_PARAM_VALIDATE_LOGIC( m_conditional_states.size() == 0,
343 BOOST_RT_PARAM_LITERAL( "matching endif command is missing" ) );
344
345 return false;
346}
347
348//____________________________________________________________________________//
349
350boost::optional<cstring>
351config_file_iterator::Impl::get_macro_value( cstring macro_name, bool ignore_missing )
352{
353 symbol_table_t::const_iterator it = m_symbols_table.find( macro_name );
354
355 if( it == m_symbols_table.end() ) {
356 boost::optional<cstring> macro_value; // !! variable actually may have different type
357
358 #ifndef UNDER_CE
359 env::get( macro_name, macro_value );
360 #endif
361
362 BOOST_RT_PARAM_VALIDATE_LOGIC( macro_value || ignore_missing || !m_detect_missing_macro,
363 BOOST_RT_PARAM_LITERAL( "Unknown macro \"" ) << macro_name << BOOST_RT_PARAM_LITERAL( "\"" ) );
364
365 if( !macro_value ) {
366 if( !ignore_missing )
367 macro_value = cstring();
368 }
369 else
370 m_symbols_table.add( macro_name, *macro_value );
371
372 return macro_value;
373 }
374
375 return boost::optional<cstring>( cstring( it->second ) );
376}
377
378//____________________________________________________________________________//
379
380void
381config_file_iterator::Impl::process_command_line( cstring line )
382{
383 line.trim_left( m_command_delimeter.size() );
384
385 unit_test::string_token_iterator tit( line, unit_test::max_tokens = 2 );
386
387 command_handler_map::const_iterator it = m_command_handler_map.find( *tit );
388
389 BOOST_RT_PARAM_VALIDATE_LOGIC( it != m_command_handler_map.end(), BOOST_RT_PARAM_LITERAL( "Invalid command " ) << *tit );
390
391 ++tit;
392
393 (it->second)( *tit );
394}
395
396//____________________________________________________________________________//
397
398void
399config_file_iterator::Impl::process_include( cstring line )
400{
401 using namespace unit_test;
402
403 if( !is_active_line() )
404 return;
405
406 string_token_iterator tit( line, kept_delimeters = dt_none );
407
408 BOOST_RT_PARAM_VALIDATE_LOGIC( tit != string_token_iterator(),
409 BOOST_RT_PARAM_LITERAL( "include file name missing" ) );
410
411 cstring include_file_name = *tit;
412
413 BOOST_RT_PARAM_VALIDATE_LOGIC( ++tit == string_token_iterator(),
414 BOOST_RT_PARAM_LITERAL( "unexpected tokens at the end of include command" ) );
415
416 substitute_macros( include_file_name );
417
418 m_curr_level.reset( new include_level( include_file_name, m_path_separators, m_curr_level.release() ) );
419}
420
421//____________________________________________________________________________//
422
423void
424config_file_iterator::Impl::process_define( cstring line )
425{
426 using namespace unit_test;
427
428 if( !is_active_line() )
429 return;
430
431 string_token_iterator tit( line, (kept_delimeters = dt_none, max_tokens = 2 ));
432
433 cstring macro_name = *tit;
434 BOOST_RT_PARAM_VALIDATE_LOGIC( is_valid_identifier( macro_name ),
435 BOOST_RT_PARAM_LITERAL( "invalid macro name" ) );
436
437 cstring macro_value = *(++tit);
438 substitute_macros( macro_value );
439
440 m_symbols_table.add( macro_name, macro_value );
441}
442
443//____________________________________________________________________________//
444
445void
446config_file_iterator::Impl::process_undef( cstring line )
447{
448 if( !is_active_line() )
449 return;
450
451 cstring macro_name = line;
452 BOOST_RT_PARAM_VALIDATE_LOGIC( is_valid_identifier( macro_name ),
453 BOOST_RT_PARAM_LITERAL( "invalid macro name" ) );
454
455 m_symbols_table.remove( macro_name );
456}
457
458//____________________________________________________________________________//
459
460void
461config_file_iterator::Impl::process_ifdef( cstring line )
462{
463 m_conditional_states.push_back( true );
464 if( !is_active_line() )
465 return;
466
467 cstring macro_name = line;
468 BOOST_RT_PARAM_VALIDATE_LOGIC( is_valid_identifier( macro_name ),
469 BOOST_RT_PARAM_LITERAL( "invalid macro name" ) );
470
471 if( !get_macro_value( macro_name ) )
472 m_inactive_ifdef_level = m_conditional_states.size();
473}
474
475//____________________________________________________________________________//
476
477void
478config_file_iterator::Impl::process_ifndef( cstring line )
479{
480 m_conditional_states.push_back( true );
481 if( !is_active_line() )
482 return;
483
484 cstring macro_name = line;
485 BOOST_RT_PARAM_VALIDATE_LOGIC( is_valid_identifier( macro_name ),
486 BOOST_RT_PARAM_LITERAL( "invalid macro name" ) );
487
488 if( get_macro_value( macro_name ) )
489 m_inactive_ifdef_level = m_conditional_states.size();
490}
491
492//____________________________________________________________________________//
493
494void
495config_file_iterator::Impl::process_else( cstring line )
496{
497 BOOST_RT_PARAM_VALIDATE_LOGIC( m_conditional_states.size() > 0 && m_conditional_states.back(),
498 BOOST_RT_PARAM_LITERAL( "else without matching if" ) );
499
500 m_inactive_ifdef_level = m_conditional_states.size() == m_inactive_ifdef_level ? 0 : m_conditional_states.size();
501
502 BOOST_RT_PARAM_VALIDATE_LOGIC( line.is_empty(), BOOST_RT_PARAM_LITERAL( "unexpected tokens at the end of else command" ) );
503}
504
505//____________________________________________________________________________//
506
507void
508config_file_iterator::Impl::process_endif( cstring line )
509{
510 BOOST_RT_PARAM_VALIDATE_LOGIC( m_conditional_states.size() > 0, BOOST_RT_PARAM_LITERAL( "endif without matching if" ) );
511
512 if( m_conditional_states.size() == m_inactive_ifdef_level )
513 m_inactive_ifdef_level = 0;
514
515 m_conditional_states.pop_back();
516 BOOST_RT_PARAM_VALIDATE_LOGIC( line.is_empty(), BOOST_RT_PARAM_LITERAL( "unexpected tokens at the end of endif command" ) );
517}
518
519//____________________________________________________________________________//
520
521void
522config_file_iterator::Impl::substitute_macros( cstring& where )
523{
524 m_post_subst_line.clear();
525 cstring::size_type pos;
526
527 while( (pos = where.find( m_macro_ref_begin )) != cstring::npos ) {
528 m_post_subst_line.append( where.begin(), pos );
529
530 where.trim_left( where.begin() + pos + m_macro_ref_begin.size() );
531
532 pos = where.find( m_macro_ref_end );
533
534 BOOST_RT_PARAM_VALIDATE_LOGIC( pos != cstring::npos, BOOST_RT_PARAM_LITERAL( "incomplete macro reference" ) );
535
536 cstring value = *get_macro_value( where.substr( 0, pos ), false );
537 m_post_subst_line.append( value.begin(), value.size() );
538
539 where.trim_left( where.begin() + pos + m_macro_ref_end.size() );
540 }
541
542 if( !m_post_subst_line.empty() ) {
543 m_post_subst_line.append( where.begin(), where.size() );
544 where = m_post_subst_line;
545 }
546}
547
548//____________________________________________________________________________//
549
550// ************************************************************************** //
551// ************** runtime::file::config_file_iterator ************** //
552// ************************************************************************** //
553
554void
555config_file_iterator::construct()
556{
557 m_pimpl.reset( new Impl );
558}
559
560//____________________________________________________________________________//
561
562void
563config_file_iterator::load( cstring file_name )
564{
565 m_pimpl->m_curr_level.reset( new include_level( file_name, m_pimpl->m_path_separators ) );
566 m_pimpl->m_buffer.reset( new char[m_pimpl->m_buffer_size] );
567
568 register_command_handler( m_pimpl->m_include_kw, bind( &Impl::process_include, m_pimpl.get(), _1 ) );
569 register_command_handler( m_pimpl->m_define_kw, bind( &Impl::process_define, m_pimpl.get(), _1 ) );
570 register_command_handler( m_pimpl->m_undef_kw, bind( &Impl::process_undef, m_pimpl.get(), _1 ) );
571 register_command_handler( m_pimpl->m_ifdef_kw, bind( &Impl::process_ifdef, m_pimpl.get(), _1 ) );
572 register_command_handler( m_pimpl->m_ifndef_kw, bind( &Impl::process_ifndef, m_pimpl.get(), _1 ) );
573 register_command_handler( m_pimpl->m_else_kw, bind( &Impl::process_else, m_pimpl.get(), _1 ) );
574 register_command_handler( m_pimpl->m_endif_kw, bind( &Impl::process_endif, m_pimpl.get(), _1 ) );
575
576 init();
577}
578
579//____________________________________________________________________________//
580
581location const&
582config_file_iterator::curr_location()
583{
584 return m_pimpl->m_curr_level->m_curr_location;
585}
586
587//____________________________________________________________________________//
588
589void
590config_file_iterator::register_command_handler( cstring command_kw, command_handler const& ch )
591{
592 m_pimpl->m_command_handler_map.add( command_kw, ch );
593}
594
595//____________________________________________________________________________//
596
597bool
598config_file_iterator::get()
599{
600 return m_pimpl->get_next_line( m_value );
601}
602
603//____________________________________________________________________________//
604
605void
606config_file_iterator::set_parameter( rtti::id_t id, cstring value )
607{
608 BOOST_RTTI_SWITCH( id ) {
609 BOOST_RTTI_CASE( cfg_detail::path_separators_t )
610 assign_op( m_pimpl->m_path_separators , value, 0 );
611 BOOST_RTTI_CASE( cfg_detail::sl_comment_delimeter_t )
612 assign_op( m_pimpl->m_sl_comment_delimeter , value, 0 );
613 BOOST_RTTI_CASE( cfg_detail::command_delimeter_t )
614 assign_op( m_pimpl->m_command_delimeter , value, 0 );
615 BOOST_RTTI_CASE( cfg_detail::line_beak_t )
616 assign_op( m_pimpl->m_line_beak , value, 0 );
617 BOOST_RTTI_CASE( cfg_detail::macro_ref_begin_t )
618 assign_op( m_pimpl->m_macro_ref_begin , value, 0 );
619 BOOST_RTTI_CASE( cfg_detail::macro_ref_end_t )
620 assign_op( m_pimpl->m_macro_ref_end , value, 0 );
621 BOOST_RTTI_CASE( cfg_detail::include_kw_t )
622 assign_op( m_pimpl->m_include_kw , value, 0 );
623 BOOST_RTTI_CASE( cfg_detail::define_kw_t )
624 assign_op( m_pimpl->m_define_kw , value, 0 );
625 BOOST_RTTI_CASE( cfg_detail::undef_kw_t )
626 assign_op( m_pimpl->m_undef_kw , value, 0 );
627 BOOST_RTTI_CASE( cfg_detail::ifdef_kw_t )
628 assign_op( m_pimpl->m_ifdef_kw , value, 0 );
629 BOOST_RTTI_CASE( cfg_detail::ifndef_kw_t )
630 assign_op( m_pimpl->m_ifndef_kw , value, 0 );
631 BOOST_RTTI_CASE( cfg_detail::else_kw_t )
632 assign_op( m_pimpl->m_else_kw , value, 0 );
633 BOOST_RTTI_CASE( cfg_detail::endif_kw_t )
634 assign_op( m_pimpl->m_endif_kw , value, 0 );
635 }
636}
637
638//____________________________________________________________________________//
639
640void
641config_file_iterator::set_parameter( rtti::id_t id, bool value )
642{
643 BOOST_RTTI_SWITCH( id ) {
644 BOOST_RTTI_CASE( cfg_detail::trim_leading_spaces_t )
645 m_pimpl->m_trim_leading_spaces = value;
646 BOOST_RTTI_CASE( cfg_detail::trim_trailing_spaces_t )
647 m_pimpl->m_trim_trailing_spaces = value;
648 BOOST_RTTI_CASE( cfg_detail::skip_empty_lines_t )
649 m_pimpl->m_skip_empty_lines = value;
650 BOOST_RTTI_CASE( cfg_detail::detect_missing_macro_t )
651 m_pimpl->m_detect_missing_macro = value;
652 }
653}
654
655//____________________________________________________________________________//
656
657void
658config_file_iterator::set_parameter( rtti::id_t id, char_type value )
659{
660 BOOST_RTTI_SWITCH( id ) {
661 BOOST_RTTI_CASE( cfg_detail::line_delimeter_t )
662 m_pimpl->m_line_delimeter = value;
663 }
664}
665
666//____________________________________________________________________________//
667
668void
669config_file_iterator::set_parameter( rtti::id_t id, std::size_t value )
670{
671 BOOST_RTTI_SWITCH( id ) {
672 BOOST_RTTI_CASE( cfg_detail::buffer_size_t )
673 m_pimpl->m_buffer_size = value;
674 }
675}
676
677//____________________________________________________________________________//
678
679} // namespace file
680
681} // namespace BOOST_RT_PARAM_NAMESPACE
682
683} // namespace boost
684
685// EOF