// Copyright (c) 2018-2021 Emil Dotchevski and Reverge Studios, 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 the program presented in // https://boostorg.github.io/leaf/#introduction-result, converted to use // outcome::result instead of leaf::result. // It reads a text file in a buffer and prints it to std::cout, using LEAF to // handle errors. This version does not use exception handling. #include #include #include #include #include namespace outcome = boost::outcome_v2; namespace leaf = boost::leaf; // First, we need an enum to define our error codes: enum error_code { bad_command_line = 1, open_error, read_error, size_error, eof_error, output_error }; template using result = outcome::std_result; // To enable LEAF to work with outcome::result, we need to specialize the // is_result_type template: namespace boost { namespace leaf { template struct is_result_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 // result: // Parse the command line, return the file name. result parse_command_line( int argc, char const * argv[] ); // Open a file for reading. result> file_open( char const * file_name ); // Return the size of the file. result file_size( FILE & f ); // Read size bytes from f into buf. 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::try_handle_all( [&]() -> result { BOOST_LEAF_AUTO(file_name, parse_command_line(argc,argv)); auto load = leaf::on_error( leaf::e_file_name{file_name} ); BOOST_LEAF_AUTO(f, file_open(file_name)); BOOST_LEAF_AUTO(s, file_size(*f)); std::string buffer(1 + s, '\0'); BOOST_LEAF_CHECK(file_read(*f, &buffer[0], buffer.size()-1)); std::cout << buffer; std::cout.flush(); if( std::cout.fail() ) return leaf::new_error(output_error, leaf::e_errno{errno}).to_error_code(); 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 objects. // This handler will be called if the error includes: // - an object of type error_code equal to 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_value, 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 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 size_error, // read_error, eof_error, and // - an optional 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; if( errn ) std::cerr << ", errno=" << *errn; std::cerr << std::endl; return 3; }, // This handler will be called if the error includes: // - an object of type error_code equal to output_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 we've got a 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. []( 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. result parse_command_line( int argc, char const * argv[] ) { if( argc==2 ) return argv[1]; else return leaf::new_error(bad_command_line).to_error_code(); } // Open a file for reading. 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(open_error, leaf::e_errno{errno}).to_error_code(); } // Return the size of the file. result file_size( FILE & f ) { auto load = leaf::on_error([] { return leaf::e_errno{errno}; }); if( fseek(&f, 0, SEEK_END) ) return leaf::new_error(size_error).to_error_code(); int s = ftell(&f); if( s==-1L ) return leaf::new_error(size_error).to_error_code(); if( fseek(&f,0,SEEK_SET) ) return leaf::new_error(size_error).to_error_code(); return s; } // Read size bytes from f into buf. result file_read( FILE & f, void * buf, int size ) { int n = fread(buf, 1, size, &f); if( ferror(&f) ) return leaf::new_error(read_error, leaf::e_errno{errno}).to_error_code(); if( n!=size ) return leaf::new_error(eof_error).to_error_code(); return outcome::success(); } //////////////////////////////////////// #ifdef BOOST_LEAF_NO_EXCEPTIONS namespace boost { BOOST_LEAF_NORETURN void throw_exception( std::exception const & e ) { std::cerr << "Terminating due to a C++ exception under BOOST_LEAF_NO_EXCEPTIONS: " << e.what(); std::terminate(); } struct source_location; BOOST_LEAF_NORETURN void throw_exception( std::exception const & e, boost::source_location const & ) { throw_exception(e); } } #endif