struct MyFileBlob; static PL_blob_t my_file_blob = PL_BLOB_DEFINITION(MyFileBlob, "my_file_blob"); static const PlOptionsFlag<int> MyFileBlob_options("MyFileBlob-options", { {"absolute", PL_FILE_ABSOLUTE}, {"ospath", PL_FILE_OSPATH}, {"search", PL_FILE_SEARCH}, {"exist", PL_FILE_EXIST}, {"read", PL_FILE_READ}, {"write", PL_FILE_WRITE}, {"execute", PL_FILE_EXECUTE}, {"noerrors", PL_FILE_NOERRORS} }); struct MyFileBlob : public PlBlob { std::FILE* file_; std::string mode_; int flags_; std::string filename_; std::vector<char> buffer_; // used by read(), to avoid re-allocation explicit MyFileBlob() : PlBlob(&my_file_blob) { } explicit MyFileBlob(PlTerm filename, PlTerm mode, PlTerm flags) : PlBlob(&my_file_blob), mode_(mode.as_string()) { flags_ = MyFileBlob_options.lookup_list(flags); filename_ = filename.get_file_name(flags_); file_ = fopen(filename_.c_str(), mode_.c_str()); if ( !file_ ) // TODO: get error code (might not be existence error) throw PlExistenceError("my_file_blob_open", PlTerm_string(filename_)); // for debugging: // PlTerm_string(filename.as_string() + "\" => \"" + // filename_ + "\", \"" + mode_ + // ", flags=" + MyFileBlob_options.as_string(flags_) + "\")") } PL_BLOB_SIZE std::string read(size_t count) { assert(sizeof buffer_[0] == sizeof (char)); assert(sizeof (char) == 1); buffer_.reserve(count); return std::string(buffer_.data(), std::fread(buffer_.data(), sizeof buffer_[0], count, file_)); } bool eof() const { return std::feof(file_); } bool error() const { return std::ferror(file_); } virtual ~MyFileBlob() noexcept { if ( !close() ) // Can't use PL_warning() Sdprintf("***ERROR: Close MyFileBlob failed: (%s)\n", filename_.c_str()); } bool close() noexcept { if ( !file_ ) return true; int rc = std::fclose(file_); file_ = nullptr; return rc == 0; } PlException MyFileBlobError(const std::string error) const { return PlGeneralError(PlCompound(error, PlTermv(symbol_term()))); } int compare_fields(const PlBlob* _b_data) const override { // dynamic_cast is safer than static_cast, but slower (see documentation) // It's used here for testing (the documentation has static_cast) auto b_data = dynamic_cast<const MyFileBlob*>(_b_data); return filename_.compare(b_data->filename_); } bool write_fields(IOSTREAM *s, int flags) const override { PlStream strm(s); strm.printf(","); return write_fields_only(strm); } bool write_fields_only(PlStream& strm) const { // For debugging: // strm.printf("%s mode=%s flags=%s", filename_.c_str(), mode_.c_str(), // MyFileBlob_options.as_string(flags_).c_str()); strm.printf("%s", filename_.c_str()); if ( !file_ ) strm.printf("-CLOSED"); return true; } bool portray(PlStream& strm) const { strm.printf("MyFileBlob("); write_fields_only(strm); strm.printf(")"); return true; } }; PREDICATE(my_file_open, 4) { auto ref = std::unique_ptr<PlBlob>(new MyFileBlob(A2, A3, A4)); return A1.unify_blob(&ref); } PREDICATE(my_file_close, 1) { auto ref = PlBlobV<MyFileBlob>::cast_ex(A1, my_file_blob); if ( !ref->close() ) // TODO: get the error code throw ref->MyFileBlobError("my_file_blob_close_error"); return true; }