// Copyright (c) 2018 Emil Dotchevski // Copyright (c) 2018 Second Spectrum, Inc. // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // This is a short but complete program that reads a text file in a buffer and prints it to std::cout, // using LEAF to handle errors. It does not use exception handling. #include #include #include #include #include namespace leaf = boost::leaf; // First, we need an enum to define our error codes: enum error_code { bad_command_line = 1, input_file_open_error, input_file_size_error, input_file_read_error, input_eof_error, cout_error }; // To enable leaf::error to work with our error_code enum, we need to specialize the is_e_type template: namespace boost { namespace leaf { template<> struct is_e_type: std::true_type { }; } } // We will handle all failures in our main function, but first, here are the declarations of the functions it calls, each // communicating failures using leaf::result: // Parse the command line, return the file name. leaf::result parse_command_line( int argc, char const * argv[ ] ); // Open a file for reading. leaf::result> file_open( char const * file_name ); // Return the size of the file. leaf::result file_size( FILE & f ); // Read size bytes from f into buf. leaf::result file_read( FILE & f, void * buf, int size ); // The main function, which handles all errors. int main( int argc, char const * argv[ ] ) { return leaf::handle_all( [&]() -> leaf::result { leaf::result file_name = parse_command_line(argc,argv); if( !file_name ) return file_name.error(); auto propagate = leaf::preload( leaf::e_file_name{*file_name} ); leaf::result> f = file_open(*file_name); if( !f ) return f.error(); leaf::result s = file_size(**f); if( !s ) return s.error(); std::string buffer( 1 + *s, '\0' ); leaf::result fr = file_read(**f, &buffer[0], buffer.size()-1); if( !fr ) return fr.error(); std::cout << buffer; std::cout.flush(); if( std::cout.fail() ) return leaf::new_error( cout_error, leaf::e_errno{errno} ); return 0; }, // Each of the lambdas below is an error handler. LEAF will consider them, in order, and call the first one that matches // the available error information. // This handler will be called if the error includes: // - an object of type error_code equal to input_file_open_error, and // - an object of type leaf::e_errno that has .value equal to ENOENT, and // - an object of type leaf::e_file_name. [ ]( leaf::match, leaf::match, leaf::e_file_name const & fn ) { std::cerr << "File not found: " << fn.value << std::endl; return 1; }, // This handler will be called if the error includes: // - an object of type error_code equal to input_file_open_error, and // - an object of type leaf::e_errno (regardless of its .value), and // - an object of type leaf::e_file_name. [ ]( leaf::match, leaf::e_errno const & errn, leaf::e_file_name const & fn ) { std::cerr << "Failed to open " << fn.value << ", errno=" << errn << std::endl; return 2; }, // This handler will be called if the error includes: // - an object of type error_code equal to any of input_file_size_error, input_file_read_error, input_eof_error, and // - an object of type leaf::e_errno (regardless of its .value), and // - an object of type leaf::e_file_name. [ ]( leaf::match, leaf::e_errno const & errn, leaf::e_file_name const & fn ) { std::cerr << "Failed to access " << fn.value << ", errno=" << errn << std::endl; return 3; }, // This handler will be called if the error includes: // - an object of type error_code equal to cout_error, and // - an object of type leaf::e_errno (regardless of its .value), [ ]( leaf::match, leaf::e_errno const & errn ) { std::cerr << "Output error, errno=" << errn << std::endl; return 4; }, // This handler will be called if the error includes an object of type error_code equal to bad_command_line. [ ]( leaf::match ) { std::cout << "Bad command line argument" << std::endl; return 5; }, // This last handler matches any error: it prints diagnostic information to help debug logic errors in the program, since it // failed to match an appropriate error handler to the error condition it encountered. In this program this handler will // never be called, but it is required by handle_all because, well, it must handle all errors (the alternative is to use // handle_some instead, which doesn't require a "catch all" last-resort handler; instead, if it fails to find a suitable // handler for an error, it returns the error to its caller.) [ ]( leaf::error_info const & unmatched ) { std::cerr << "Unknown failure detected" << std::endl << "Cryptic diagnostic information follows" << std::endl << unmatched; return 6; } ); } // Implementations of the functions called by main: // Parse the command line, return the file name. leaf::result parse_command_line( int argc, char const * argv[ ] ) { if( argc==2 ) return argv[1]; else return leaf::new_error(bad_command_line); } // Open a file for reading. leaf::result> file_open( char const * file_name ) { if( FILE * f = fopen(file_name,"rb") ) return std::shared_ptr(f,&fclose); else return leaf::new_error( input_file_open_error, leaf::e_errno{errno} ); } // Return the size of the file. leaf::result file_size( FILE & f ) { auto propagate = leaf::defer([ ] { return leaf::e_errno{errno}; } ); if( fseek(&f,0,SEEK_END) ) return leaf::new_error( input_file_size_error ); int s = ftell(&f); if( s==-1L ) return leaf::new_error( input_file_size_error ); if( fseek(&f,0,SEEK_SET) ) return leaf::new_error( input_file_size_error ); return s; } // Read size bytes from f into buf. leaf::result file_read( FILE & f, void * buf, int size ) { int n = fread(buf,1,size,&f); if( ferror(&f) ) return leaf::new_error( input_file_read_error, leaf::e_errno{errno} ); if( n!=size ) return leaf::new_error( input_eof_error ); return { }; }