Mercurial > octave
view libinterp/corefcn/oct-stream.cc @ 23219:3ac9f9ecfae5 stable
maint: Update copyright dates.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Wed, 22 Feb 2017 12:39:29 -0500 |
parents | e9a0469dedd9 |
children | 092078913d54 fadab2fc567f |
line wrap: on
line source
/* Copyright (C) 1996-2017 John W. Eaton Copyright (C) 2015-2016 Lachlan Andrew, Monash University This file is part of Octave. Octave is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Octave is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Octave; see the file COPYING. If not, see <http://www.gnu.org/licenses/>. */ #if defined (HAVE_CONFIG_H) # include "config.h" #endif #include <cassert> #include <cctype> #include <cstring> #include <deque> #include <fstream> #include <iomanip> #include <iostream> #include <sstream> #include <string> #include "Array.h" #include "Cell.h" #include "byte-swap.h" #include "lo-ieee.h" #include "lo-mappers.h" #include "lo-utils.h" #include "oct-locbuf.h" #include "quit.h" #include "singleton-cleanup.h" #include "str-vec.h" #include "error.h" #include "errwarn.h" #include "input.h" #include "interpreter.h" #include "octave.h" #include "oct-stdstrm.h" #include "oct-stream.h" #include "ov.h" #include "ovl.h" #include "utils.h" // Programming Note: There are two very different error functions used // in the stream code. When invoked with "error (...)" the member // function from octave_stream or octave_base_stream is called. This // function sets the error state on the stream AND returns control to // the caller. The caller must then return a value at the end of the // function. When invoked with "::error (...)" the exception-based // error function from error.h is used. This function will throw an // exception and not return control to the caller. BE CAREFUL and // invoke the correct error function! // Possible values for conv_err: // // 1 : not a real scalar // 2 : value is NaN // 3 : value is not an integer static int convert_to_valid_int (const octave_value& tc, int& conv_err) { conv_err = 0; int retval = 0; double dval = 0.0; try { dval = tc.double_value (); } catch (const octave::execution_exception&) { recover_from_exception (); conv_err = 1; } if (! conv_err) { if (! lo_ieee_isnan (dval)) { int ival = octave::math::nint (dval); if (ival == dval) retval = ival; else conv_err = 3; } else conv_err = 2; } return retval; } static octave_idx_type get_size (double d, const std::string& who) { octave_idx_type retval = -1; if (lo_ieee_isnan (d)) ::error ("%s: NaN is invalid as size specification", who.c_str ()); if (octave::math::isinf (d)) retval = -1; else { if (d < 0.0) ::error ("%s: negative value invalid as size specification", who.c_str ()); if (d > std::numeric_limits<octave_idx_type>::max ()) ::error ("%s: dimension too large for Octave's index type", who.c_str ()); retval = octave::math::nint_big (d); } return retval; } static void get_size (const Array<double>& size, octave_idx_type& nr, octave_idx_type& nc, bool& one_elt_size_spec, const std::string& who) { nr = -1; nc = -1; one_elt_size_spec = false; double dnr = -1.0; double dnc = -1.0; octave_idx_type sz_len = size.numel (); if (sz_len == 1) { one_elt_size_spec = true; dnr = size(0); dnc = (dnr == 0.0) ? 0.0 : 1.0; } else if (sz_len == 2) { dnr = size(0); if (octave::math::isinf (dnr)) ::error ("%s: invalid size specification", who.c_str ()); dnc = size(1); } else ::error ("%s: invalid size specification", who.c_str ()); nr = get_size (dnr, who); if (dnc >= 0.0) nc = get_size (dnc, who); } class scanf_format_elt { public: enum special_conversion { whitespace_conversion = 1, literal_conversion = 2, null = 3 }; scanf_format_elt (const char *txt = 0, int w = 0, bool d = false, char typ = '\0', char mod = '\0', const std::string& ch_class = "") : text (strsave (txt)), width (w), discard (d), type (typ), modifier (mod), char_class (ch_class) { } scanf_format_elt (const scanf_format_elt& e) : text (strsave (e.text)), width (e.width), discard (e.discard), type (e.type), modifier (e.modifier), char_class (e.char_class) { } scanf_format_elt& operator = (const scanf_format_elt& e) { if (this != &e) { text = strsave (e.text); width = e.width; discard = e.discard; type = e.type; modifier = e.modifier; char_class = e.char_class; } return *this; } ~scanf_format_elt (void) { delete [] text; } // The C-style format string. const char *text; // The maximum field width. int width; // TRUE if we are not storing the result of this conversion. bool discard; // Type of conversion -- 'd', 'i', 'o', 'u', 'x', 'e', 'f', 'g', // 'c', 's', 'p', '%', or '['. char type; // A length modifier -- 'h', 'l', or 'L'. char modifier; // The class of characters in a '[' format. std::string char_class; }; class scanf_format_list { public: scanf_format_list (const std::string& fmt = ""); ~scanf_format_list (void); octave_idx_type num_conversions (void) { return nconv; } // The length can be different than the number of conversions. // For example, "x %d y %d z" has 2 conversions but the length of // the list is 3 because of the characters that appear after the // last conversion. size_t length (void) const { return fmt_elts.size (); } const scanf_format_elt *first (void) { curr_idx = 0; return current (); } const scanf_format_elt *current (void) const { return length () > 0 ? fmt_elts[curr_idx] : 0; } const scanf_format_elt *next (bool cycle = true) { static scanf_format_elt dummy (0, 0, false, scanf_format_elt::null, '\0', ""); curr_idx++; if (curr_idx >= length ()) { if (cycle) curr_idx = 0; else return &dummy; } return current (); } void printme (void) const; bool ok (void) const { return (nconv >= 0); } operator bool () const { return ok (); } bool all_character_conversions (void); bool all_numeric_conversions (void); private: // Number of conversions specified by this format string, or -1 if // invalid conversions have been found. octave_idx_type nconv; // Index to current element; size_t curr_idx; // List of format elements. std::deque<scanf_format_elt*> fmt_elts; // Temporary buffer. std::ostringstream buf; void add_elt_to_list (int width, bool discard, char type, char modifier, const std::string& char_class = ""); void process_conversion (const std::string& s, size_t& i, size_t n, int& width, bool& discard, char& type, char& modifier); int finish_conversion (const std::string& s, size_t& i, size_t n, int& width, bool discard, char& type, char modifier); // No copying! scanf_format_list (const scanf_format_list&); scanf_format_list& operator = (const scanf_format_list&); }; scanf_format_list::scanf_format_list (const std::string& s) : nconv (0), curr_idx (0), fmt_elts (), buf () { size_t n = s.length (); size_t i = 0; int width = 0; bool discard = false; char modifier = '\0'; char type = '\0'; bool have_more = true; while (i < n) { have_more = true; if (s[i] == '%') { // Process percent-escape conversion type. process_conversion (s, i, n, width, discard, type, modifier); have_more = (buf.tellp () != 0); } else if (isspace (s[i])) { type = scanf_format_elt::whitespace_conversion; width = 0; discard = false; modifier = '\0'; buf << " "; while (++i < n && isspace (s[i])) ; // skip whitespace add_elt_to_list (width, discard, type, modifier); have_more = false; } else { type = scanf_format_elt::literal_conversion; width = 0; discard = false; modifier = '\0'; while (i < n && ! isspace (s[i]) && s[i] != '%') buf << s[i++]; add_elt_to_list (width, discard, type, modifier); have_more = false; } if (nconv < 0) { have_more = false; break; } } if (have_more) add_elt_to_list (width, discard, type, modifier); buf.clear (); buf.str (""); } scanf_format_list::~scanf_format_list (void) { size_t n = fmt_elts.size (); for (size_t i = 0; i < n; i++) { scanf_format_elt *elt = fmt_elts[i]; delete elt; } } void scanf_format_list::add_elt_to_list (int width, bool discard, char type, char modifier, const std::string& char_class) { std::string text = buf.str (); if (! text.empty ()) { scanf_format_elt *elt = new scanf_format_elt (text.c_str (), width, discard, type, modifier, char_class); fmt_elts.push_back (elt); } buf.clear (); buf.str (""); } static std::string expand_char_class (const std::string& s) { std::string retval; size_t len = s.length (); size_t i = 0; while (i < len) { unsigned char c = s[i++]; if (c == '-' && i > 1 && i < len && ( static_cast<unsigned char> (s[i-2]) <= static_cast<unsigned char> (s[i]))) { // Add all characters from the range except the first (we // already added it below). for (c = s[i-2]+1; c < s[i]; c++) retval += c; } else { // Add the character to the class. Only add '-' if it is // the last character in the class. if (c != '-' || i == len) retval += c; } } return retval; } void scanf_format_list::process_conversion (const std::string& s, size_t& i, size_t n, int& width, bool& discard, char& type, char& modifier) { width = 0; discard = false; modifier = '\0'; type = '\0'; buf << s[i++]; bool have_width = false; while (i < n) { switch (s[i]) { case '*': if (discard) nconv = -1; else { discard = true; buf << s[i++]; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (have_width) nconv = -1; else { char c = s[i++]; width = 10 * width + c - '0'; have_width = true; buf << c; while (i < n && isdigit (s[i])) { c = s[i++]; width = 10 * width + c - '0'; buf << c; } } break; case 'h': case 'l': case 'L': if (modifier != '\0') nconv = -1; else modifier = s[i++]; break; case 'd': case 'i': case 'o': case 'u': case 'x': if (modifier == 'L') { nconv = -1; break; } goto fini; case 'e': case 'f': case 'g': if (modifier == 'h') { nconv = -1; break; } // No float or long double conversions, thanks. buf << 'l'; goto fini; case 'c': case 's': case 'p': case '%': case '[': if (modifier != '\0') { nconv = -1; break; } goto fini; fini: { if (finish_conversion (s, i, n, width, discard, type, modifier) == 0) return; } break; default: nconv = -1; break; } if (nconv < 0) break; } nconv = -1; } int scanf_format_list::finish_conversion (const std::string& s, size_t& i, size_t n, int& width, bool discard, char& type, char modifier) { int retval = 0; std::string char_class; size_t beg_idx = std::string::npos; size_t end_idx = std::string::npos; if (s[i] == '%') { type = '%'; buf << s[i++]; } else { type = s[i]; if (s[i] == '[') { buf << s[i++]; if (i < n) { beg_idx = i; if (s[i] == '^') { type = '^'; buf << s[i++]; if (i < n) { beg_idx = i; if (s[i] == ']') buf << s[i++]; } } else if (s[i] == ']') buf << s[i++]; } while (i < n && s[i] != ']') buf << s[i++]; if (i < n && s[i] == ']') { end_idx = i-1; buf << s[i++]; } if (s[i-1] != ']') retval = nconv = -1; } else buf << s[i++]; nconv++; } if (nconv >= 0) { if (beg_idx != std::string::npos && end_idx != std::string::npos) char_class = expand_char_class (s.substr (beg_idx, end_idx - beg_idx + 1)); add_elt_to_list (width, discard, type, modifier, char_class); } return retval; } void scanf_format_list::printme (void) const { size_t n = fmt_elts.size (); for (size_t i = 0; i < n; i++) { scanf_format_elt *elt = fmt_elts[i]; std::cerr << "width: " << elt->width << "\n" << "discard: " << elt->discard << "\n" << "type: "; if (elt->type == scanf_format_elt::literal_conversion) std::cerr << "literal text\n"; else if (elt->type == scanf_format_elt::whitespace_conversion) std::cerr << "whitespace\n"; else std::cerr << elt->type << "\n"; std::cerr << "modifier: " << elt->modifier << "\n" << "char_class: '" << undo_string_escapes (elt->char_class) << "'\n" << "text: '" << undo_string_escapes (elt->text) << "'\n\n"; } } bool scanf_format_list::all_character_conversions (void) { size_t n = fmt_elts.size (); if (n > 0) { for (size_t i = 0; i < n; i++) { scanf_format_elt *elt = fmt_elts[i]; switch (elt->type) { case 'c': case 's': case '%': case '[': case '^': case scanf_format_elt::literal_conversion: case scanf_format_elt::whitespace_conversion: break; default: return false; break; } } return true; } else return false; } bool scanf_format_list::all_numeric_conversions (void) { size_t n = fmt_elts.size (); if (n > 0) { for (size_t i = 0; i < n; i++) { scanf_format_elt *elt = fmt_elts[i]; switch (elt->type) { case 'd': case 'i': case 'o': case 'u': case 'x': case 'e': case 'f': case 'g': break; default: return false; break; } } return true; } else return false; } class printf_format_elt { public: printf_format_elt (const char *txt = 0, int n = 0, int w = -1, int p = -1, const std::string& f = "", char typ = '\0', char mod = '\0') : text (strsave (txt)), args (n), fw (w), prec (p), flags (f), type (typ), modifier (mod) { } printf_format_elt (const printf_format_elt& e) : text (strsave (e.text)), args (e.args), fw (e.fw), prec (e.prec), flags (e.flags), type (e.type), modifier (e.modifier) { } printf_format_elt& operator = (const printf_format_elt& e) { if (this != &e) { text = strsave (e.text); args = e.args; fw = e.fw; prec = e.prec; flags = e.flags; type = e.type; modifier = e.modifier; } return *this; } ~printf_format_elt (void) { delete [] text; } // The C-style format string. const char *text; // How many args do we expect to consume? int args; // Field width. int fw; // Precision. int prec; // Flags -- '-', '+', ' ', '0', or '#'. std::string flags; // Type of conversion -- 'd', 'i', 'o', 'x', 'X', 'u', 'c', 's', // 'f', 'e', 'E', 'g', 'G', 'p', or '%' char type; // A length modifier -- 'h', 'l', or 'L'. char modifier; }; class printf_format_list { public: printf_format_list (const std::string& fmt = ""); ~printf_format_list (void); octave_idx_type num_conversions (void) { return nconv; } const printf_format_elt *first (void) { curr_idx = 0; return current (); } const printf_format_elt *current (void) const { return length () > 0 ? fmt_elts[curr_idx] : 0; } size_t length (void) const { return fmt_elts.size (); } const printf_format_elt *next (bool cycle = true) { curr_idx++; if (curr_idx >= length ()) { if (cycle) curr_idx = 0; else return 0; } return current (); } bool last_elt_p (void) { return (curr_idx + 1 == length ()); } void printme (void) const; bool ok (void) const { return (nconv >= 0); } operator bool () const { return ok (); } private: // Number of conversions specified by this format string, or -1 if // invalid conversions have been found. octave_idx_type nconv; // Index to current element; size_t curr_idx; // List of format elements. std::deque<printf_format_elt*> fmt_elts; // Temporary buffer. std::ostringstream buf; void add_elt_to_list (int args, const std::string& flags, int fw, int prec, char type, char modifier); void process_conversion (const std::string& s, size_t& i, size_t n, int& args, std::string& flags, int& fw, int& prec, char& modifier, char& type); void finish_conversion (const std::string& s, size_t& i, int args, const std::string& flags, int fw, int prec, char modifier, char& type); // No copying! printf_format_list (const printf_format_list&); printf_format_list& operator = (const printf_format_list&); }; printf_format_list::printf_format_list (const std::string& s) : nconv (0), curr_idx (0), fmt_elts (), buf () { size_t n = s.length (); size_t i = 0; int args = 0; std::string flags; int fw = -1; int prec = -1; char modifier = '\0'; char type = '\0'; bool have_more = true; bool empty_buf = true; if (n == 0) { printf_format_elt *elt = new printf_format_elt ("", args, fw, prec, flags, type, modifier); fmt_elts.push_back (elt); } else { while (i < n) { have_more = true; empty_buf = (buf.tellp () == 0); switch (s[i]) { case '%': { if (empty_buf) { process_conversion (s, i, n, args, flags, fw, prec, type, modifier); // If there is nothing in the buffer, then // add_elt_to_list must have just been called, so we // are already done with the current element and we // don't need to call add_elt_to_list if this is our // last trip through the loop. have_more = (buf.tellp () != 0); } else add_elt_to_list (args, flags, fw, prec, type, modifier); } break; default: { args = 0; flags = ""; fw = -1; prec = -1; modifier = '\0'; type = '\0'; buf << s[i++]; empty_buf = false; } break; } if (nconv < 0) { have_more = false; break; } } if (have_more) add_elt_to_list (args, flags, fw, prec, type, modifier); buf.clear (); buf.str (""); } } printf_format_list::~printf_format_list (void) { size_t n = fmt_elts.size (); for (size_t i = 0; i < n; i++) { printf_format_elt *elt = fmt_elts[i]; delete elt; } } void printf_format_list::add_elt_to_list (int args, const std::string& flags, int fw, int prec, char type, char modifier) { std::string text = buf.str (); if (! text.empty ()) { printf_format_elt *elt = new printf_format_elt (text.c_str (), args, fw, prec, flags, type, modifier); fmt_elts.push_back (elt); } buf.clear (); buf.str (""); } void printf_format_list::process_conversion (const std::string& s, size_t& i, size_t n, int& args, std::string& flags, int& fw, int& prec, char& modifier, char& type) { args = 0; flags = ""; fw = -1; prec = -1; modifier = '\0'; type = '\0'; buf << s[i++]; bool nxt = false; while (i < n) { switch (s[i]) { case '-': case '+': case ' ': case '0': case '#': flags += s[i]; buf << s[i++]; break; default: nxt = true; break; } if (nxt) break; } if (i < n) { if (s[i] == '*') { fw = -2; args++; buf << s[i++]; } else { if (isdigit (s[i])) { int nn = 0; std::string tmp = s.substr (i); sscanf (tmp.c_str (), "%d%n", &fw, &nn); } while (i < n && isdigit (s[i])) buf << s[i++]; } } if (i < n && s[i] == '.') { // nothing before the . means 0. if (fw == -1) fw = 0; // . followed by nothing is 0. prec = 0; buf << s[i++]; if (i < n) { if (s[i] == '*') { prec = -2; args++; buf << s[i++]; } else { if (isdigit (s[i])) { int nn = 0; std::string tmp = s.substr (i); sscanf (tmp.c_str (), "%d%n", &prec, &nn); } while (i < n && isdigit (s[i])) buf << s[i++]; } } } if (i < n) { // Accept and record modifier, but don't place it in the format // item text. All integer conversions are handled as 64-bit // integers. switch (s[i]) { case 'h': case 'l': case 'L': modifier = s[i++]; break; default: break; } } if (i < n) finish_conversion (s, i, args, flags, fw, prec, modifier, type); else nconv = -1; } void printf_format_list::finish_conversion (const std::string& s, size_t& i, int args, const std::string& flags, int fw, int prec, char modifier, char& type) { switch (s[i]) { case 'd': case 'i': case 'o': case 'x': case 'X': case 'u': case 'c': if (modifier == 'L') { nconv = -1; break; } goto fini; case 'f': case 'e': case 'E': case 'g': case 'G': if (modifier == 'h' || modifier == 'l') { nconv = -1; break; } goto fini; case 's': case 'p': case '%': if (modifier != '\0') { nconv = -1; break; } goto fini; fini: type = s[i]; buf << s[i++]; if (type != '%' || args != 0) nconv++; if (type != '%') args++; add_elt_to_list (args, flags, fw, prec, type, modifier); break; default: nconv = -1; break; } } void printf_format_list::printme (void) const { size_t n = fmt_elts.size (); for (size_t i = 0; i < n; i++) { printf_format_elt *elt = fmt_elts[i]; std::cerr << "args: " << elt->args << "\n" << "flags: '" << elt->flags << "'\n" << "width: " << elt->fw << "\n" << "prec: " << elt->prec << "\n" << "type: '" << elt->type << "'\n" << "modifier: '" << elt->modifier << "'\n" << "text: '" << undo_string_escapes (elt->text) << "'\n\n"; } } // Calculate x^n. Used for ...e+nn so that, for example, 1e2 is // exactly 100 and 5e-1 is 1/2 static double pown (double x, unsigned int n) { double retval = 1; for (unsigned int d = n; d; d >>= 1) { if (d & 1) retval *= x; x *= x; } return retval; } static Cell init_inf_nan (void) { Cell retval (dim_vector (1, 2)); retval(0) = Cell (octave_value ("inf")); retval(1) = Cell (octave_value ("nan")); return retval; } namespace octave { // Delimited stream, optimized to read strings of characters separated // by single-character delimiters. // // The reason behind this class is that octstream doesn't provide // seek/tell, but the opportunity has been taken to optimise for the // textscan workload. // // The function reads chunks into a 4kiB buffer, and marks where the // last delimiter occurs. Reads up to this delimiter can be fast. // After that last delimiter, the remaining text is moved to the front // of the buffer and the buffer is refilled. This also allows cheap // seek and tell operations within a "fast read" block. class delimited_stream { public: delimited_stream (std::istream& is, const std::string& delimiters, int longest_lookahead, octave_idx_type bsize = 4096); delimited_stream (std::istream& is, const delimited_stream& ds); ~delimited_stream (void); // Called when optimized sequence of get is finished. Ensures that // there is a remaining delimiter in buf, or loads more data in. void field_done (void) { if (idx >= last) refresh_buf (); } // Load new data into buffer, and set eob, last, idx. // Return EOF at end of file, 0 otherwise. int refresh_buf (void); // Get a character, relying on caller to call field_done if // a delimiter has been reached. int get (void) { if (delimited) return eof () ? std::istream::traits_type::eof () : *idx++; else return get_undelim (); } // Get a character, checking for underrun of the buffer. int get_undelim (void); // Read character that will be got by the next get. int peek (void) { return eof () ? std::istream::traits_type::eof () : *idx; } // Read character that will be got by the next get. int peek_undelim (void); // Undo a 'get' or 'get_undelim'. It is the caller's responsibility // to avoid overflow by calling putbacks only for a character got by // get() or get_undelim(), with no intervening // get, get_delim, field_done, refresh_buf, getline, read or seekg. void putback (char /*ch*/ = 0) { if (! eof ()) --idx; } int getline (std::string& dest, char delim); // int skipline (char delim); char *read (char *buffer, int size, char* &new_start); // Return a position suitable to "seekg", valid only within this // block between calls to field_done. char *tellg (void) { return idx; } void seekg (char *old_idx) { idx = old_idx; } bool eof (void) { return (eob == buf && i_stream.eof ()) || (flags & std::ios_base::eofbit); } operator const void* (void) { return (! eof () && ! flags) ? this : 0; } bool fail (void) { return flags & std::ios_base::failbit; } std::ios_base::iostate rdstate (void) { return flags; } void setstate (std::ios_base::iostate m) { flags = flags | m; } void clear (std::ios_base::iostate m = (std::ios_base::eofbit & ~std::ios_base::eofbit)) { flags = flags & m; } // Report if any characters have been consumed. // (get, read, etc. not cancelled by putback or seekg) void progress_benchmark (void) { progress_marker = idx; } bool no_progress (void) { return progress_marker == idx; } private: // Number of characters to read from the file at once. int bufsize; // Stream to read from. std::istream& i_stream; // Temporary storage for a "chunk" of data. char *buf; // Current read pointer. char *idx; // Location of last delimiter in the buffer at buf (undefined if // delimited is false). char *last; // Position after last character in buffer. char *eob; // True if there is delimiter in the bufer after idx. bool delimited; // Longest lookahead required. int longest; // Sequence of single-character delimiters. const std::string delims; // Position of start of buf in original stream. std::streampos buf_in_file; // Marker to see if a read consumes any characters. char *progress_marker; std::ios_base::iostate flags; // No copying! delimited_stream (const delimited_stream&); delimited_stream& operator = (const delimited_stream&); }; // Create a delimited stream, reading from is, with delimiters delims, // and allowing reading of up to tellg + longest_lookeahead. When is // is at EOF, lookahead may be padded by ASCII nuls. delimited_stream::delimited_stream (std::istream& is, const std::string& delimiters, int longest_lookahead, octave_idx_type bsize) : bufsize (bsize), i_stream (is), longest (longest_lookahead), delims (delimiters), flags (std::ios::failbit & ~std::ios::failbit) // can't cast 0 { buf = new char[bufsize]; eob = buf + bufsize; idx = eob; // refresh_buf shouldn't try to copy old data progress_marker = idx; refresh_buf (); // load the first batch of data } // Used to create a stream from a strstream from data read from a dstr. // FIXME: Find a more efficient approach. Perhaps derived dstr delimited_stream::delimited_stream (std::istream& is, const delimited_stream& ds) : bufsize (ds.bufsize), i_stream (is), longest (ds.longest), delims (ds.delims), flags (std::ios::failbit & ~std::ios::failbit) // can't cast 0 { buf = new char[bufsize]; eob = buf + bufsize; idx = eob; // refresh_buf shouldn't try to copy old data progress_marker = idx; refresh_buf (); // load the first batch of data } delimited_stream::~delimited_stream (void) { // Seek to the correct position in i_stream. if (! eof ()) { i_stream.clear (); i_stream.seekg (buf_in_file); i_stream.read (buf, idx - buf); } delete [] buf; } // Read a character from the buffer, refilling the buffer from the file // if necessary. int delimited_stream::get_undelim (void) { int retval; if (eof ()) { setstate (std::ios_base::failbit); return std::istream::traits_type::eof (); } if (idx < eob) retval = *idx++; else { refresh_buf (); if (eof ()) { setstate (std::ios_base::eofbit); retval = std::istream::traits_type::eof (); } else retval = *idx++; } if (idx >= last) delimited = false; return retval; } // Return the next character to be read without incrementing the // pointer, refilling the buffer from the file if necessary. int delimited_stream::peek_undelim (void) { int retval = get_undelim (); putback (); return retval; } // Copy remaining unprocessed data to the start of the buffer and load // new data to fill it. Return EOF if the file is at EOF before // reading any data and all of the data that has been read has been // processed. int delimited_stream::refresh_buf (void) { if (eof ()) return std::istream::traits_type::eof (); int retval; if (eob < idx) idx = eob; size_t old_remaining = eob - idx; octave_quit (); // allow ctrl-C if (old_remaining > 0) { buf_in_file += (idx - buf); memmove (buf, idx, old_remaining); } progress_marker -= idx - buf; // where original idx would have been idx = buf; int gcount; // chars read if (! i_stream.eof ()) { buf_in_file = i_stream.tellg (); // record for destructor i_stream.read (buf + old_remaining, bufsize - old_remaining); gcount = i_stream.gcount (); } else gcount = 0; eob = buf + old_remaining + gcount; last = eob; if (gcount == 0) { delimited = false; if (eob != buf) // no more data in file, but still some to go retval = 0; else // file and buffer are both done. retval = std::istream::traits_type::eof (); } else { delimited = true; for (last = eob - longest; last - buf >= 0; last--) { if (delims.find (*last) != std::string::npos) break; } if (last < buf) delimited = false; retval = 0; } // Ensure fast peek doesn't give valid char if (retval == std::istream::traits_type::eof ()) *idx = '\0'; // FIXME: check that no TreatAsEmpty etc starts w. \0? return retval; } // Return a pointer to a block of data of size size, assuming that a // sufficiently large buffer is available in buffer, if required. // If called when delimited == true, and size is no greater than // longest_lookahead then this will not call refresh_buf, so seekg // still works. Otherwise, seekg may be invalidated. char * delimited_stream::read (char *buffer, int size, char* &prior_tell) { char *retval; if (eob - idx > size) { retval = idx; idx += size; if (idx > last) delimited = false; } else { // If there was a tellg pointing to an earlier point than the current // read position, try to keep it in the active buffer. // In the current code, prior_tell==idx for each call, // so this is not necessary, just a precaution. if (eob - prior_tell + size < bufsize) { octave_idx_type gap = idx - prior_tell; idx = prior_tell; refresh_buf (); idx += gap; } else // can't keep the tellg in range. May skip some data. { refresh_buf (); } prior_tell = buf; if (eob - idx > size) { retval = idx; idx += size; if (idx > last) delimited = false; } else { if (size <= bufsize) // small read, but reached EOF { retval = idx; memset (eob, 0, size + (idx - buf)); idx += size; } else // Reading more than the whole buf; return it in buffer { retval = buffer; // FIXME: read bufsize at a time int i; for (i = 0; i < size && ! eof (); i++) *buffer++ = get_undelim (); if (eof ()) memset (buffer, 0, size - i); } } } return retval; } // Return in OUT an entire line, terminated by delim. On input, OUT // must have length at least 1. int delimited_stream::getline (std::string& out, char delim) { int len = out.length (), used = 0; int ch; while ((ch = get_undelim ()) != delim && ch != std::istream::traits_type::eof ()) { out[used++] = ch; if (used == len) { len <<= 1; out.resize (len); } } out.resize (used); field_done (); return ch; } // A single conversion specifier, such as %f or %c. class textscan_format_elt { public: enum special_conversion { whitespace_conversion = 1, literal_conversion = 2 }; textscan_format_elt (const std::string& txt, int w = 0, int p = -1, int bw = 0, bool dis = false, char typ = '\0', const std::string& ch_class = std::string ()) : text (txt), width (w), prec (p), bitwidth (bw), char_class (ch_class), type (typ), discard (dis), numeric (typ == 'd' || typ == 'u' || type == 'f' || type == 'n') { } textscan_format_elt (const textscan_format_elt& e) : text (e.text), width (e.width), prec (e.prec), bitwidth (e.bitwidth), char_class (e.char_class), type (e.type), discard (e.discard), numeric (e.numeric) { } textscan_format_elt& operator = (const textscan_format_elt& e) { if (this != &e) { text = e.text; width = e.width; prec = e.prec; bitwidth = e.bitwidth; discard = e.discard; type = e.type; numeric = e.numeric; char_class = e.char_class; } return *this; } // The C-style format string. std::string text; // The maximum field width. unsigned int width; // The maximum number of digits to read after the decimal in a // floating point conversion. int prec; // The size of the result. For integers, bitwidth may be 8, 16, 34, // or 64. For floating point values, bitwidth may be 32 or 64. int bitwidth; // The class of characters in a `[' or `^' format. std::string char_class; // Type of conversion // -- `d', `u', `f', `n', `s', `q', `c', `%', `C', `D', `[' or `^'. char type; // TRUE if we are not storing the result of this conversion. bool discard; // TRUE if the type is 'd', 'u', 'f', 'n' bool numeric; }; // The (parsed) sequence of format specifiers. class textscan; class textscan_format_list { public: textscan_format_list (const std::string& fmt = std::string (), const std::string& who = "textscan"); ~textscan_format_list (void); octave_idx_type num_conversions (void) const { return nconv; } // The length can be different than the number of conversions. // For example, "x %d y %d z" has 2 conversions but the length of // the list is 3 because of the characters that appear after the // last conversion. size_t numel (void) const { return fmt_elts.size (); } const textscan_format_elt *first (void) { curr_idx = 0; return current (); } const textscan_format_elt *current (void) const { return numel () > 0 ? fmt_elts[curr_idx] : 0; } const textscan_format_elt *next (bool cycle = true) { curr_idx++; if (curr_idx >= numel ()) { if (cycle) curr_idx = 0; else return 0; } return current (); } void printme (void) const; bool ok (void) const { return (nconv >= 0); } operator const void* (void) const { return ok () ? this : 0; } // What function name should be shown when reporting errors. std::string who; // True if number of %f to be set from data file. bool set_from_first; // At least one conversion specifier is s,q,c, or [...]. bool has_string; int read_first_row (delimited_stream& is, textscan& ts); std::list<octave_value> out_buf (void) const { return (output_container); } private: // Number of conversions specified by this format string, or -1 if // invalid conversions have been found. octave_idx_type nconv; // Index to current element; size_t curr_idx; // List of format elements. std::deque<textscan_format_elt*> fmt_elts; // list holding column arrays of types specified by conversions std::list<octave_value> output_container; // Temporary buffer. std::ostringstream buf; void add_elt_to_list (unsigned int width, int prec, int bitwidth, octave_value val_type, bool discard, char type, const std::string& char_class = std::string ()); void process_conversion (const std::string& s, size_t& i, size_t n); std::string parse_char_class (const std::string& pattern) const; int finish_conversion (const std::string& s, size_t& i, size_t n, unsigned int& width, int& prec, int& bitwidth, octave_value& val_type, bool discard, char& type); // No copying! textscan_format_list (const textscan_format_list&); textscan_format_list& operator = (const textscan_format_list&); }; // Main class to implement textscan. Read data and parse it // according to a format. // // The calling sequence is // // textscan scanner (); // scanner.scan (...); class OCTINTERP_API textscan { public: textscan (const std::string& who_arg = "textscan"); ~textscan (void) { } octave_value scan (std::istream& isp, const std::string& fmt, octave_idx_type ntimes, const octave_value_list& options, octave_idx_type& read_count); private: friend class textscan_format_list; // What function name should be shown when reporting errors. std::string who; std::string buf; // Three cases for delim_table and delim_list // 1. delim_table empty, delim_list empty: whitespace delimiters // 2. delim_table = look-up table of delim chars, delim_list empty. // 3. delim_table non-empty, delim_list = Cell array of delim strings std::string whitespace_table; // delim_table[i] == '\0' if i is not a delimiter. std::string delim_table; // String of delimiter characters. std::string delims; Cell comment_style; // How far ahead to look to detect an open comment. int comment_len; // First character of open comment. int comment_char; octave_idx_type buffer_size; std::string date_locale; // 'inf' and 'nan' for formatted_double. Cell inf_nan; // Array of strings of delimiters. Cell delim_list; // Longest delimiter. int delim_len; octave_value empty_value; std::string exp_chars; int header_lines; Cell treat_as_empty; // Longest string to treat as "N/A". int treat_as_empty_len; std::string whitespace; short eol1; short eol2; short return_on_error; bool collect_output; bool multiple_delims_as_one; bool default_exp; bool numeric_delim; octave_idx_type lines; octave_value do_scan (std::istream& isp, textscan_format_list& fmt_list, octave_idx_type ntimes); void parse_options (const octave_value_list& args, textscan_format_list& fmt_list); int read_format_once (delimited_stream& isp, textscan_format_list& fmt_list, std::list<octave_value>& retval, Array<octave_idx_type> row, int& done_after); void scan_one (delimited_stream& is, const textscan_format_elt& fmt, octave_value& ov, Array<octave_idx_type> row); // Methods to process a particular conversion specifier. double read_double (delimited_stream& is, const textscan_format_elt& fmt) const; void scan_complex (delimited_stream& is, const textscan_format_elt& fmt, Complex& val) const; int scan_bracket (delimited_stream& is, const std::string& pattern, std::string& val) const; int scan_caret (delimited_stream& is, const std::string& pattern, std::string& val) const; void scan_string (delimited_stream& is, const textscan_format_elt& fmt, std::string& val) const; void scan_cstring (delimited_stream& is, const textscan_format_elt& fmt, std::string& val) const; void scan_qstring (delimited_stream& is, const textscan_format_elt& fmt, std::string& val); // Helper methods. std::string read_until (delimited_stream& is, const Cell& delimiters, const std::string& ends) const; int lookahead (delimited_stream& is, const Cell& targets, int max_len, bool case_sensitive = true) const; bool match_literal (delimited_stream& isp, const textscan_format_elt& elem); int skip_whitespace (delimited_stream& is, bool EOLstop = false); int skip_delim (delimited_stream& is); bool is_delim (unsigned char ch) const { return ((delim_table.empty () && (isspace (ch) || ch == eol1 || ch == eol2)) || delim_table[ch] != '\0'); } bool isspace (unsigned int ch) const { return whitespace_table[ch & 0xff]; } // True if the only delimiter is whitespace. bool whitespace_delim (void) const { return delim_table.empty (); } // No copying! textscan (const textscan&); textscan& operator = (const textscan&); }; textscan_format_list::textscan_format_list (const std::string& s, const std::string& who_arg) : who (who_arg), set_from_first (false), has_string (false), nconv (0), curr_idx (0), fmt_elts (), buf () { size_t n = s.length (); size_t i = 0; unsigned int width = -1; // Unspecified width = max (except %c) int prec = -1; int bitwidth = 0; bool discard = false; char type = '\0'; bool have_more = true; if (s.empty ()) { buf.clear (); buf.str (""); buf << "%f"; bitwidth = 64; type = 'f'; add_elt_to_list (width, prec, bitwidth, octave_value (NDArray ()), discard, type); have_more = false; set_from_first = true; nconv = 1; } else { set_from_first = false; while (i < n) { have_more = true; if (s[i] == '%' && (i+1 == n || s[i+1] != '%')) { // Process percent-escape conversion type. process_conversion (s, i, n); // If there is nothing in the buffer, then add_elt_to_list // must have just been called, so we are already done with // the current element and we don't need to call // add_elt_to_list if this is our last trip through the // loop. have_more = (buf.tellp () != 0); } else if (isspace (s[i])) { while (++i < n && isspace (s[i])) /* skip whitespace */; have_more = false; } else { type = textscan_format_elt::literal_conversion; width = 0; prec = -1; bitwidth = 0; discard = true; while (i < n && ! isspace (s[i]) && (s[i] != '%' || (i+1 < n && s[i+1] == '%'))) { if (s[i] == '%') // if double %, skip one i++; buf << s[i++]; width++; } add_elt_to_list (width, prec, bitwidth, octave_value (), discard, type); have_more = false; } if (nconv < 0) { have_more = false; break; } } } if (have_more) add_elt_to_list (width, prec, bitwidth, octave_value (), discard, type); buf.clear (); buf.str (""); } textscan_format_list::~textscan_format_list (void) { size_t n = numel (); for (size_t i = 0; i < n; i++) { textscan_format_elt *elt = fmt_elts[i]; delete elt; } } void textscan_format_list::add_elt_to_list (unsigned int width, int prec, int bitwidth, octave_value val_type, bool discard, char type, const std::string& char_class) { std::string text = buf.str (); if (! text.empty ()) { textscan_format_elt *elt = new textscan_format_elt (text, width, prec, bitwidth, discard, type, char_class); if (! discard) output_container.push_back (val_type); fmt_elts.push_back (elt); } buf.clear (); buf.str (""); } void textscan_format_list::process_conversion (const std::string& s, size_t& i, size_t n) { unsigned width = 0; int prec = -1; int bitwidth = 0; bool discard = false; octave_value val_type; char type = '\0'; buf << s[i++]; bool have_width = false; while (i < n) { switch (s[i]) { case '*': if (discard) nconv = -1; else { discard = true; buf << s[i++]; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (have_width) nconv = -1; else { char c = s[i++]; width = width * 10 + c - '0'; have_width = true; buf << c; while (i < n && isdigit (s[i])) { c = s[i++]; width = width * 10 + c - '0'; buf << c; } if (i < n && s[i] == '.') { buf << s[i++]; prec = 0; while (i < n && isdigit (s[i])) { c = s[i++]; prec = prec * 10 + c - '0'; buf << c; } } } break; case 'd': case 'u': { bool done = true; buf << (type = s[i++]); if (i < n) { if (s[i] == '8') { bitwidth = 8; if (type == 'd') val_type = octave_value (int8NDArray ()); else val_type = octave_value (uint8NDArray ()); buf << s[i++]; } else if (s[i] == '1' && i+1 < n && s[i+1] == '6') { bitwidth = 16; if (type == 'd') val_type = octave_value (int16NDArray ()); else val_type = octave_value (uint16NDArray ()); buf << s[i++]; buf << s[i++]; } else if (s[i] == '3' && i+1 < n && s[i+1] == '2') { done = false; // use default size below buf << s[i++]; buf << s[i++]; } else if (s[i] == '6' && i+1 < n && s[i+1] == '4') { bitwidth = 64; if (type == 'd') val_type = octave_value (int64NDArray ()); else val_type = octave_value (uint64NDArray ()); buf << s[i++]; buf << s[i++]; } else done = false; } else done = false; if (! done) { bitwidth = 32; if (type == 'd') val_type = octave_value (int32NDArray ()); else val_type = octave_value (uint32NDArray ()); } goto fini; } case 'f': buf << (type = s[i++]); bitwidth = 64; if (i < n) { if (s[i] == '3' && i+1 < n && s[i+1] == '2') { bitwidth = 32; val_type = octave_value (FloatNDArray ()); buf << s[i++]; buf << s[i++]; } else if (s[i] == '6' && i+1 < n && s[i+1] == '4') { val_type = octave_value (NDArray ()); buf << s[i++]; buf << s[i++]; } else val_type = octave_value (NDArray ()); } else val_type = octave_value (NDArray ()); goto fini; case 'n': buf << (type = s[i++]); bitwidth = 64; val_type = octave_value (NDArray ()); goto fini; case 's': case 'q': case '[': case 'c': if (! discard) val_type = octave_value (Cell ()); buf << (type = s[i++]); has_string = true; goto fini; fini: { if (! have_width) { if (type == 'c') // %c defaults to one character width = 1; else width = static_cast<unsigned int> (-1); // others: unlimited } if (finish_conversion (s, i, n, width, prec, bitwidth, val_type, discard, type) == 0) return; } break; default: error ("%s: '%%%c' is not a valid format specifier", who.c_str (), s[i]); } if (nconv < 0) break; } nconv = -1; } // Parse [...] and [^...] // // Matlab does not expand expressions like A-Z, but they are useful, and // so we parse them "carefully". We treat '-' as a usual character // unless both start and end characters are from the same class (upper // case, lower case, numeric), or this is not the first '-' in the // pattern. // // Keep both a running list of characters and a mask of which chars have // occurred. The first is efficient for patterns with few characters. // The latter is efficient for [^...] patterns. std::string textscan_format_list::parse_char_class (const std::string& pattern) const { int len = pattern.length (); if (len == 0) return ""; std::string retval (256, '\0'); std::string mask (256, '\0'); // number of times chr has been seen int in = 0, out = 0; unsigned char ch, prev = 0; bool flip = false; ch = pattern[in]; if (ch == '^') { in++; flip = true; } mask[pattern[in]] = '\1'; retval[out++] = pattern[in++]; // even copy ']' if it is first bool prev_was_range = false; // disallow "a-m-z" as a pattern bool prev_prev_was_range = false; for (; in < len; in++) { bool was_range = false; ch = pattern[in]; if (ch == ']') break; if (prev == '-' && in > 1 && isalnum (ch) && ! prev_prev_was_range) { unsigned char start_of_range = pattern[in-2]; if (start_of_range < ch && ((isupper (ch) && isupper (start_of_range)) || (islower (ch) && islower (start_of_range)) || (isdigit (ch) && isdigit (start_of_range)) || mask['-'] > 1)) // not the first '-' { was_range = true; out--; mask['-']--; for (int i = start_of_range; i <= ch; i++) { if (mask[i] == '\0') { mask[i] = '\1'; retval[out++] = i; } } } } if (! was_range) { if (mask[ch]++ == 0) retval[out++] = ch; else if (ch != '-') warning_with_id ("octave:textscan-pattern", "%s: [...] contains two '%c's", who.c_str (), ch); if (prev == '-' && mask['-'] >= 2) warning_with_id ("octave:textscan-pattern", "%s: [...] contains two '-'s outside range expressions", who.c_str ()); } prev = ch; prev_prev_was_range = prev_was_range; prev_was_range = was_range; } if (flip) // [^...] { out = 0; for (int i = 0; i < 256; i++) if (! mask[i]) retval[out++] = i; } retval.resize (out); return retval; } int textscan_format_list::finish_conversion (const std::string& s, size_t& i, size_t n, unsigned int& width, int& prec, int& bitwidth, octave_value& val_type, bool discard, char& type) { int retval = 0; std::string char_class; size_t beg_idx = std::string::npos; size_t end_idx = std::string::npos; if (type != '%') { nconv++; if (type == '[') { if (i < n) { beg_idx = i; if (s[i] == '^') { type = '^'; buf << s[i++]; if (i < n) { beg_idx = i; if (s[i] == ']') buf << s[i++]; } } else if (s[i] == ']') buf << s[i++]; } while (i < n && s[i] != ']') buf << s[i++]; if (i < n && s[i] == ']') { end_idx = i-1; buf << s[i++]; } if (s[i-1] != ']') retval = nconv = -1; } } if (nconv >= 0) { if (beg_idx != std::string::npos && end_idx != std::string::npos) char_class = parse_char_class (s.substr (beg_idx, end_idx - beg_idx + 1)); add_elt_to_list (width, prec, bitwidth, val_type, discard, type, char_class); } return retval; } void textscan_format_list::printme (void) const { size_t n = numel (); for (size_t i = 0; i < n; i++) { textscan_format_elt *elt = fmt_elts[i]; std::cerr << "width: " << elt->width << "\n" << "digits " << elt->prec << "\n" << "bitwidth: " << elt->bitwidth << "\n" << "discard: " << elt->discard << "\n" << "type: "; if (elt->type == textscan_format_elt::literal_conversion) std::cerr << "literal text\n"; else if (elt->type == textscan_format_elt::whitespace_conversion) std::cerr << "whitespace\n"; else std::cerr << elt->type << "\n"; std::cerr << "char_class: `" << undo_string_escapes (elt->char_class) << "'\n" << "text: `" << undo_string_escapes (elt->text) << "'\n\n"; } } // If FORMAT is explicitly "", it is assumed to be "%f" repeated enough // times to read the first row of the file. Set it now. int textscan_format_list::read_first_row (delimited_stream& is, textscan& ts) { // Read first line and strip end-of-line, which may be two characters std::string first_line (20, ' '); is.getline (first_line, static_cast<char> (ts.eol2)); if (! first_line.empty () && first_line[first_line.length () - 1] == ts.eol1) first_line.resize (first_line.length () - 1); std::istringstream strstr (first_line); delimited_stream ds (strstr, is); dim_vector dv (1,1); // initial size of each output_container Complex val; octave_value val_type; nconv = 0; int max_empty = 1000; // failsafe, if ds fails but not with eof int retval = 0; // read line, creating output_container as we go while (! ds.eof ()) { bool already_skipped_delim = false; ts.skip_whitespace (ds); ds.progress_benchmark (); bool progress = false; ts.scan_complex (ds, *fmt_elts[0], val); if (ds.fail ()) { ds.clear (ds.rdstate () & ~std::ios::failbit); if (ds.eof ()) break; // Unless this was a missing value (i.e., followed by a delimiter), // return with an error status. ts.skip_delim (ds); if (ds.no_progress ()) { retval = 4; break; } already_skipped_delim = true; val = ts.empty_value.scalar_value (); if (! --max_empty) break; } if (val.imag () == 0) val_type = octave_value (NDArray (dv, val.real ())); else val_type = octave_value (ComplexNDArray (dv, val)); output_container.push_back (val_type); if (! already_skipped_delim) ts.skip_delim (ds); if (! progress && ds.no_progress ()) break; nconv++; } output_container.pop_front (); // discard empty element from constructor // Create fmt_list now that the size is known for (octave_idx_type i = 1; i < nconv; i++) fmt_elts.push_back (new textscan_format_elt (*fmt_elts[0])); return retval; // May have returned 4 above. } textscan::textscan (const std::string& who_arg) : who (who_arg), buf (), whitespace_table (), delim_table (), delims (), comment_style (), comment_len (0), comment_char (-2), buffer_size (0), date_locale (), inf_nan (init_inf_nan ()), empty_value (octave::numeric_limits<double>::NaN ()), exp_chars ("edED"), header_lines (0), treat_as_empty (), treat_as_empty_len (0), whitespace (" \b\t"), eol1 ('\r'), eol2 ('\n'), return_on_error (1), collect_output (false), multiple_delims_as_one (false), default_exp (true), numeric_delim (false), lines (0) { } octave_value textscan::scan (std::istream& isp, const std::string& fmt, octave_idx_type ntimes, const octave_value_list& options, octave_idx_type& count) { textscan_format_list fmt_list (fmt); parse_options (options, fmt_list); octave_value result = do_scan (isp, fmt_list, ntimes); // FIXME: this is probably not the best way to get count. The // position could easily be larger than octave_idx_type when using // 32-bit indexing. std::ios::iostate state = isp.rdstate (); isp.clear (); count = static_cast<octave_idx_type> (isp.tellg ()); isp.setstate (state); return result; } octave_value textscan::do_scan (std::istream& isp, textscan_format_list& fmt_list, octave_idx_type ntimes) { octave_value retval; if (fmt_list.num_conversions () == -1) error ("%s: invalid format specified", who.c_str ()); if (fmt_list.num_conversions () == 0) error ("%s: no valid format conversion specifiers", who.c_str ()); // skip the first header_lines std::string dummy; for (int i = 0; i < header_lines && isp; i++) getline (isp, dummy, static_cast<char> (eol2)); // Create our own buffered stream, for fast get/putback/tell/seek. // First, see how far ahead it should let us look. int max_lookahead = std::max (std::max (comment_len, treat_as_empty_len), std::max (delim_len, 3)); // 3 for NaN and Inf // Next, choose a buffer size to avoid reading too much, or too often. octave_idx_type buf_size = 4096; if (buffer_size) buf_size = buffer_size; else if (ntimes > 0) { // Avoid overflow of 80*ntimes... buf_size = std::min (buf_size, std::max (ntimes, 80 * ntimes)); buf_size = std::max (buf_size, ntimes); } // Finally, create the stream. delimited_stream is (isp, (delim_table.empty () ? whitespace + "\r\n" : delims), max_lookahead, buf_size); // Grow retval dynamically. "size" is half the initial size // (FIXME: Should we start smaller if ntimes is large?) octave_idx_type size = ((ntimes < 8 && ntimes >= 0) ? ntimes : 1); Array<octave_idx_type> row_idx (dim_vector (1,2)); row_idx(1) = 0; int err = 0; octave_idx_type row = 0; if (multiple_delims_as_one) // bug #44750? skip_delim (is); int done_after; // Number of columns read when EOF seen. // If FORMAT explicitly "", read first line and see how many "%f" match if (fmt_list.set_from_first) { err = fmt_list.read_first_row (is, *this); lines = 1; done_after = fmt_list.numel () + 1; if (! err) row = 1; // the above puts the first line into fmt_list.out_buf () } else done_after = fmt_list.out_buf ().size () + 1; std::list<octave_value> out = fmt_list.out_buf (); // We will later merge adjacent columns of the same type. // Check now which columns to merge. // Reals may become complex, and so we can't trust types // after reading in data. // If the format was "", that conversion may already have happened, // so force all to be merged (as all are %f). bool merge_with_prev[fmt_list.numel ()]; int conv = 0; if (collect_output) { int prev_type = -1; for (std::list<octave_value>::iterator col = out.begin (); col != out.end (); col++) { if (col->type_id () == prev_type || (fmt_list.set_from_first && prev_type != -1)) merge_with_prev [conv++] = true; else merge_with_prev [conv++] = false; prev_type = col->type_id (); } } // This should be caught by earlier code, but this avoids a possible // infinite loop below. if (fmt_list.num_conversions () == 0) error ("%s: No conversions specified", who.c_str ()); // Read the data. This is the main loop. if (! err) { for (/* row set ~30 lines above */; row < ntimes || ntimes == -1; row++) { if (row == 0 || row >= size) { size += size+1; for (std::list<octave_value>::iterator col = out.begin (); col != out.end (); col++) *col = (*col).resize (dim_vector (size, 1), 0); } row_idx(0) = row; err = read_format_once (is, fmt_list, out, row_idx, done_after); if ((err & ~1) > 0 || ! is || (lines >= ntimes && ntimes > -1)) break; } } if ((err & 4) && ! return_on_error) error ("%s: Read error in field %d of row %d", who.c_str (), done_after + 1, row + 1); // If file does not end in EOL, do not pad columns with NaN. bool uneven_columns = false; if (err & 4) uneven_columns = true; else if (isp.eof ()) { isp.clear (); isp.seekg (-1, std::ios_base::end); int last_char = isp.get (); isp.setstate (isp.eofbit); uneven_columns = (last_char != eol1 && last_char != eol2); } // convert return value to Cell array Array<octave_idx_type> ra_idx (dim_vector (1,2)); // (err & 1) means "error, and no columns read this row // FIXME: This may redundant now that done_after=0 says the same if (err & 1) done_after = out.size () + 1; int valid_rows = (row == ntimes) ? ntimes : (((err & 1) && (err & 8)) ? row : row+1); dim_vector dv (valid_rows, 1); ra_idx(0) = 0; int i = 0; if (! collect_output) { retval = Cell (dim_vector (1, out.size ())); for (std::list<octave_value>::iterator col = out.begin (); col != out.end (); col++, i++) { // trim last columns if that was requested if (i == done_after && uneven_columns) dv = dim_vector (std::max (valid_rows - 1, 0), 1); ra_idx(1) = i; retval = do_cat_op (retval, octave_value (Cell (col->resize (dv,0))), ra_idx); } } else // group adjacent cells of the same type into a single cell { octave_value cur; // current cell, accumulating columns octave_idx_type group_size = 0; // columns in this cell int prev_type = -1; conv = 0; retval = Cell (); for (std::list<octave_value>::iterator col = out.begin (); col != out.end (); col++) { if (! merge_with_prev [conv++]) // including first time { if (prev_type != -1) { ra_idx(1) = i++; retval = do_cat_op (retval, octave_value (Cell (cur)), ra_idx); } cur = octave_value (col->resize (dv,0)); group_size = 1; prev_type = col->type_id (); } else { ra_idx(1) = group_size++; cur = do_cat_op (cur, octave_value (col->resize (dv,0)), ra_idx); } } ra_idx(1) = i; retval = do_cat_op (retval, octave_value (Cell (cur)), ra_idx); } return retval; } // Read a double considering the "precision" field of FMT and the // EXP_CHARS option of OPTIONS. double textscan::read_double (delimited_stream& is, const textscan_format_elt& fmt) const { int sign = 1; unsigned int width_left = fmt.width; double retval = 0; bool valid = false; // syntactically correct double? int ch = is.peek (); if (ch == '+') { is.get (); ch = is.peek (); if (width_left) width_left--; } else if (ch == '-') { sign = -1; is.get (); ch = is.peek (); if (width_left) width_left--; } // Read integer part if (ch != '.') { if (ch >= '0' && ch <= '9') // valid if at least one digit valid = true; while (width_left-- && is && (ch = is.get ()) >= '0' && ch <= '9') retval = retval * 10 + (ch - '0'); width_left++; } // Read fractional part, up to specified precision if (ch == '.' && width_left) { double multiplier = 1; int precision = fmt.prec; int i; if (width_left) width_left--; // Consider width of '.' if (precision == -1) precision = 1<<30; // FIXME: Should be MAXINT if (! valid) // if there was nothing before '.'... is.get (); // ...ch was a "peek", not "get". for (i = 0; i < precision; i++) { if (width_left-- && is && (ch = is.get ()) >= '0' && ch <= '9') retval += (ch - '0') * (multiplier *= 0.1); else { width_left++; break; } } // round up if we truncated and the next digit is >= 5 if ((i == precision || ! width_left) && (ch = is.get ()) >= '5' && ch <= '9') retval += multiplier; if (i > 0) valid = true; // valid if at least one digit after '.' // skip remainder after '.', to field width, to look for exponent if (i == precision) while (width_left-- && is && (ch = is.get ()) >= '0' && ch <= '9') ; // discard width_left++; } // look for exponent part in, e.g., 6.023E+23 bool used_exp = false; if (valid && width_left > 1 && exp_chars.find (ch) != std::string::npos) { int ch1 = is.peek (); if (ch1 == '-' || ch1 == '+' || (ch1 >= '0' && ch1 <= '9')) { // if 1.0e+$ or some such, this will set failbit, as we want width_left--; // count "E" int exp = 0; int exp_sign = 1; if (ch1 == '+') { if (width_left) width_left--; is.get (); } else if (ch1 == '-') { exp_sign = -1; is.get (); if (width_left) width_left--; } valid = false; while (width_left-- && is && (ch = is.get ()) >= '0' && ch <= '9') { exp = exp*10 + ch - '0'; valid = true; } width_left++; if (ch != std::istream::traits_type::eof () && width_left) is.putback (ch); double multiplier = pown (10, exp); if (exp_sign > 0) retval *= multiplier; else retval /= multiplier; used_exp = true; } } is.clear (); if (! used_exp && ch != std::istream::traits_type::eof () && width_left) is.putback (ch); // Check for +/- inf and NaN if (! valid && width_left >= 3) { int i = lookahead (is, inf_nan, 3, false); // false -> case insensitive if (i == 0) { retval = octave::numeric_limits<double>::Inf (); valid = true; } else if (i == 1) { retval = octave::numeric_limits<double>::NaN (); valid = true; } } // Check for +/- inf and NaN if (! valid && width_left >= 3) { int i = lookahead (is, inf_nan, 3, false); // false -> case insensitive if (i == 0) { retval = octave::numeric_limits<double>::Inf (); valid = true; } else if (i == 1) { retval = octave::numeric_limits<double>::NaN (); valid = true; } } if (! valid) is.setstate (std::ios::failbit); else is.setstate (is.rdstate () & ~std::ios::failbit); return retval * sign; } // Read a single number: real, complex, inf, NaN, possibly with limited // precision. Calls to this should be preceded by skip_whitespace. // Calling that inside scan_complex would violate its const declaration. void textscan::scan_complex (delimited_stream& is, const textscan_format_elt& fmt, Complex& val) const { double im = 0; double re = 0; bool as_empty = false; // did we fail but match a "treat_as_empty" string? bool inf = false; int ch = is.peek (); if (ch == '+' || ch == '-') // check for [+-][ij] with no coefficients { ch = is.get (); int ch2 = is.peek (); if (ch2 == 'i' || ch2 == 'j') { double value = 1; is.get (); // Check not -inf if (is.peek () == 'n') { char *pos = is.tellg (); std::ios::iostate state = is.rdstate (); is.get (); ch2 = is.get (); if (ch2 == 'f') { inf = true; re = (ch == '+') ? octave::numeric_limits<double>::Inf () : -octave::numeric_limits<double>::Inf (); value = 0; } else { is.clear (state); is.seekg (pos); // reset to position before look-ahead } } im = (ch == '+') ? value : -value; } else is.putback (ch); } if (! im && ! inf) // if not [+-][ij] or [+-]inf, read real normally { char *pos = is.tellg (); std::ios::iostate state = is.rdstate (); //re = octave_read_value<double> (is); re = read_double (is, fmt); // check for "treat as empty" string if (treat_as_empty.numel () && (is.fail () || octave::math::is_NaN_or_NA (Complex (re)) || re == octave::numeric_limits<double>::Inf ())) { for (int i = 0; i < treat_as_empty.numel (); i++) { if (ch == treat_as_empty (i).string_value ()[0]) { as_empty = true; // first char matches, so read the lot break; } } if (as_empty) // if first char matched... { as_empty = false; // ...look for the whole string is.clear (state); // treat_as_empty "-" causes partial read is.seekg (pos); // reset to position before failed read // treat_as_empty strings may be different sizes. // Read ahead longest, put it all back, then re-read the string // that matches. std::string look_buf (treat_as_empty_len, '\0'); char *look = is.read (&look_buf[0], look_buf.size (), pos); is.clear (state); is.seekg (pos); // reset to position before look-ahead // FIXME: is.read could invalidate pos for (int i = 0; i < treat_as_empty.numel (); i++) { std::string s = treat_as_empty (i).string_value (); if (! strncmp (s.c_str (), look, s.size ())) { as_empty = true; // read just the right amount is.read (&look_buf[0], s.size (), pos); break; } } } } if (! is.eof () && ! as_empty) { state = is.rdstate (); // before tellg, since that fails at EOF pos = is.tellg (); ch = is.peek (); // ch == EOF if read failed; no need to chk fail if (ch == 'i' || ch == 'j') // pure imaginary { is.get (); im = re; re = 0; } else if (ch == '+' || ch == '-') // see if it is real+imag[ij] { // save stream state in case we have to restore it pos = is.tellg (); state = is.rdstate (); //im = octave_read_value<double> (is); im = read_double (is, fmt); if (is.fail ()) im = 1; if (is.peek () == 'i' || is.peek () == 'j') is.get (); else { im = 0; // no valid imaginary part. Restore state is.clear (state); // eof shouldn't cause fail. is.seekg (pos); } } else if (is.eof ()) // we've read enough to be a "valid" read is.clear (state); // failed peek shouldn't cause fail } } if (as_empty) val = empty_value.scalar_value (); else val = Complex (re, im); } // Return in VAL the run of characters from IS NOT contained in PATTERN. int textscan::scan_caret (delimited_stream& is, const std::string& pattern, std::string& val) const { int c1 = std::istream::traits_type::eof (); std::ostringstream obuf; // Is this optimized for growing? while (is && ((c1 = (is && ! is.eof ()) ? is.get_undelim () : std::istream::traits_type::eof ()) != std::istream::traits_type::eof ()) && pattern.find (c1) == std::string::npos) obuf << static_cast<char> (c1); val = obuf.str (); if (c1 != std::istream::traits_type::eof ()) is.putback (c1); return c1; } // Read until one of the strings in DELIMITERS is found. For // efficiency, ENDS is a list of the last character of each delimiter. std::string textscan::read_until (delimited_stream& is, const Cell& delimiters, const std::string& ends) const { std::string retval (""); bool done = false; do { // find sequence ending with an ending char std::string next; scan_caret (is, ends.c_str (), next); retval = retval + next; // FIXME: could use repeated doubling of size int last = (! is.eof () ? is.get_undelim () : std::istream::traits_type::eof ()); if (last != std::istream::traits_type::eof ()) { retval = retval + static_cast<char> (last); for (int i = 0; i < delimiters.numel (); i++) { std::string delim = delimiters(i).string_value (); size_t start = (retval.length () > delim.length () ? retval.length () - delim.length () : 0); std::string may_match = retval.substr (start); if (may_match == delim) { done = true; retval = retval.substr (0, start); break; } } } } while (! done && is && ! is.eof ()); return retval; } // Read stream until either fmt.width chars have been read, or // options.delimiter has been found. Does *not* rely on fmt being 's'. // Used by formats like %6f to limit to 6. void textscan::scan_string (delimited_stream& is, const textscan_format_elt& fmt, std::string& val) const { if (delim_list.is_empty ()) { unsigned int i = 0; unsigned int width = fmt.width; for (i = 0; i < width; i++) { if (i+1 > val.length ()) val = val + val + ' '; // grow even if empty int ch = is.get (); if (is_delim (ch) || ch == std::istream::traits_type::eof ()) { is.putback (ch); break; } else val[i] = ch; } val = val.substr (0, i); // trim pre-allocation } else // Cell array of multi-character delimiters { std::string ends (""); for (int i = 0; i < delim_list.numel (); i++) { std::string tmp = delim_list(i).string_value (); ends += tmp.substr (tmp.length () - 1); } val = textscan::read_until (is, delim_list, ends); } } // Return in VAL the run of characters from IS contained in PATTERN. int textscan::scan_bracket (delimited_stream& is, const std::string& pattern, std::string& val) const { int c1 = std::istream::traits_type::eof (); std::ostringstream obuf; // Is this optimized for growing? while (is && pattern.find (c1 = is.get_undelim ()) != std::string::npos) obuf << static_cast<char> (c1); val = obuf.str (); if (c1 != std::istream::traits_type::eof ()) is.putback (c1); return c1; } // Return in VAL a string, either delimited by whitespace/delimiters, or // enclosed in a pair of double quotes ("..."). Enclosing quotes are // removed. A consecutive pair "" is inserted into VAL as a single ". void textscan::scan_qstring (delimited_stream& is, const textscan_format_elt& fmt, std::string& val) { skip_whitespace (is); if (is.peek () != '\"') scan_string (is, fmt, val); else { is.get (); scan_caret (is, "\"", val); // read everything until " is.get (); // swallow " while (is && is.peek () == '\"') // if double ", insert one in stream, { // and keep looking for single " is.get (); std::string val1; scan_caret (is, "\"", val1); val = val + "\"" + val1; is.get_undelim (); } } } // Read from IS into VAL a string of the next fmt.width characters, // including any whitespace or delimiters. void textscan::scan_cstring (delimited_stream& is, const textscan_format_elt& fmt, std::string& val) const { val.resize (fmt.width); for (unsigned int i = 0; is && i < fmt.width; i++) { int ch = is.get_undelim (); if (ch != std::istream::traits_type::eof ()) val[i] = ch; else { val.resize (i); break; } } } // Read a single '%...' conversion and place it in position ROW of OV. void textscan::scan_one (delimited_stream& is, const textscan_format_elt& fmt, octave_value& ov, Array<octave_idx_type> row) { skip_whitespace (is); is.clear (); octave_value val; if (fmt.numeric) { if (fmt.type == 'f' || fmt.type == 'n') { Complex v; skip_whitespace (is); scan_complex (is, fmt, v); if (! fmt.discard && ! is.fail ()) { if (fmt.bitwidth == 64) { if (ov.is_real_type () && v.imag () == 0) ov.internal_rep ()->fast_elem_insert (row(0), v.real ()); else { if (ov.is_real_type ()) // cat does type conversion ov = do_cat_op (ov, octave_value (v), row); else ov.internal_rep ()->fast_elem_insert (row(0), v); } } else { if (ov.is_real_type () && v.imag () == 0) ov.internal_rep ()->fast_elem_insert (row(0), float (v.real ())); else { if (ov.is_real_type ()) // cat does type conversion ov = do_cat_op (ov, octave_value (v), row); else ov.internal_rep ()->fast_elem_insert (row(0), FloatComplex (v)); } } } } else { double v; // Matlab docs say 1e30 etc should be valid for %d and // 1000 as a %d8 should be 127, so read as double. // Some loss of precision for d64 and u64. skip_whitespace (is); v = read_double (is, fmt); if (! fmt.discard && ! is.fail ()) switch (fmt.bitwidth) { case 64: switch (fmt.type) { case 'd': { octave_int64 vv = v; ov.internal_rep ()->fast_elem_insert (row(0), vv); } break; case 'u': { octave_uint64 vv = v; ov.internal_rep ()->fast_elem_insert (row(0), vv); } break; } break; case 32: switch (fmt.type) { case 'd': { octave_int32 vv = v; ov.internal_rep ()->fast_elem_insert (row(0), vv); } break; case 'u': { octave_uint32 vv = v; ov.internal_rep ()->fast_elem_insert (row(0), vv); } break; } break; case 16: if (fmt.type == 'd') { octave_int16 vv = v; ov.internal_rep ()->fast_elem_insert (row(0), vv); } else { octave_uint16 vv = v; ov.internal_rep ()->fast_elem_insert (row(0), vv); } break; case 8: if (fmt.type == 'd') { octave_int8 vv = v; ov.internal_rep ()->fast_elem_insert (row(0), vv); } else { octave_uint8 vv = v; ov.internal_rep ()->fast_elem_insert (row(0), vv); } break; } } if (is.fail () & ! fmt.discard) ov = do_cat_op (ov, empty_value, row); } else { std::string vv (" "); // initial buffer. Grows as needed switch (fmt.type) { case 's': scan_string (is, fmt, vv); break; case 'q': scan_qstring (is, fmt, vv); break; case 'c': scan_cstring (is, fmt, vv); break; case '[': scan_bracket (is, fmt.char_class.c_str (), vv); break; case '^': scan_caret (is, fmt.char_class.c_str (), vv); break; } if (! fmt.discard) ov.internal_rep ()->fast_elem_insert (row (0), Cell (octave_value (vv))); // FIXME: why does failbit get set at EOF, instead of eofbit? if (! vv.empty ()) is.clear (is.rdstate () & ~std::ios_base::failbit); } is.field_done (); } // Read data corresponding to the entire format string once, placing the // values in row ROW of retval. int textscan::read_format_once (delimited_stream& is, textscan_format_list& fmt_list, std::list<octave_value>& retval, Array<octave_idx_type> row, int& done_after) { const textscan_format_elt *elem = fmt_list.first (); std::list<octave_value>::iterator out = retval.begin (); bool no_conversions = true; bool done = false; bool conversion_failed = false; // Record for ReturnOnError bool nothing_worked = true; octave_quit (); for (size_t i = 0; i < fmt_list.numel (); i++) { bool this_conversion_failed = false; // Clear fail of previous numeric conversions. is.clear (); switch (elem->type) { case 'C': case 'D': warning ("%s: conversion %c not yet implemented", who.c_str (), elem->type); break; case 'u': case 'd': case 'f': case 'n': case 's': case '[': case '^': case 'q': case 'c': scan_one (is, *elem, *out, row); break; case textscan_format_elt::literal_conversion : match_literal (is, *elem); break; default: error ("Unknown format element '%c'", elem->type); } if (! is.fail ()) { if (! elem->discard) no_conversions = false; } else { is.clear (is.rdstate () & ~std::ios::failbit); if (!is.eof () && ~is_delim (is.peek ())) this_conversion_failed = true; } if (! elem->discard) out++; elem = fmt_list.next (); char *pos = is.tellg (); // FIXME: these conversions "ignore delimiters". Should they include // delimiters at the start of the conversion, or can those be skipped? if (elem->type != textscan_format_elt::literal_conversion // && elem->type != '[' && elem->type != '^' && elem->type != 'c' ) skip_delim (is); if (is.eof ()) { if (! done) done_after = i+1; // note EOF, but process others to get empty_val. done = true; } if (this_conversion_failed) { if (is.tellg () == pos && ! conversion_failed) { // done_after = first failure done_after = i; // note fail, but parse others to get empty_val conversion_failed = true; } else this_conversion_failed = false; } else if (! done && !conversion_failed) nothing_worked = false; } if (done) is.setstate (std::ios::eofbit); return no_conversions + (is.eof () ? 2 : 0) + (conversion_failed ? 4 : 0) + (nothing_worked ? 8 : 0); } void textscan::parse_options (const octave_value_list& args, textscan_format_list& fmt_list) { int last = args.length (); int n = last; if (n & 1) error ("%s: %d parameters given, but only %d values", who.c_str (), n-n/2, n/2); delim_len = 1; bool have_delims = false; for (int i = 0; i < last; i += 2) { std::string param = args(i).xstring_value ("%s: Invalid parameter type <%s> for parameter %d", who.c_str (), args(i).type_name ().c_str (), i/2 + 1); std::transform (param.begin (), param.end (), param.begin (), ::tolower); if (param == "delimiter") { bool invalid = true; if (args(i+1).is_string ()) { invalid = false; have_delims = true; delims = args(i+1).string_value (); if (args(i+1).is_sq_string ()) delims = do_string_escapes (delims); } else if (args(i+1).is_cell ()) { invalid = false; delim_list = args(i+1).cell_value (); delim_table = " "; // non-empty, to flag non-default delim // Check that all elements are strings, and find max length for (int j = 0; j < delim_list.numel (); j++) { if (! delim_list(j).is_string ()) invalid = true; else { if (delim_list(j).is_sq_string ()) delim_list(j) = do_string_escapes (delim_list(j) .string_value ()); octave_idx_type len = delim_list(j).string_value () .length (); delim_len = std::max (static_cast<int> (len), delim_len); } } } if (invalid) error ("%s: Delimiters must be either a string or cell array of strings", who.c_str ()); } else if (param == "commentstyle") { if (args(i+1).is_string ()) { // check here for names like "C++", "C", "shell", ...? comment_style = Cell (args(i+1)); } else if (args(i+1).is_cell ()) { comment_style = args(i+1).cell_value (); int len = comment_style.numel (); if ((len >= 1 && ! comment_style (0).is_string ()) || (len >= 2 && ! comment_style (1).is_string ()) || (len >= 3)) error ("%s: CommentStyle must be either a string or cell array of one or two strings", who.c_str ()); } else error ("%s: CommentStyle must be either a string or cell array of one or two strings", who.c_str ()); // How far ahead do we need to look to detect an open comment // and which character do we look for? if (comment_style.numel () >= 1) { comment_len = comment_style (0).string_value ().size (); comment_char = comment_style (0).string_value ()[0]; } } else if (param == "treatasempty") { bool invalid = false; if (args(i+1).is_string ()) { treat_as_empty = Cell (args(i+1)); treat_as_empty_len = args(i+1).string_value ().size (); } else if (args(i+1).is_cell ()) { treat_as_empty = args(i+1).cell_value (); for (int j = 0; j < treat_as_empty.numel (); j++) if (! treat_as_empty (j).is_string ()) invalid = true; else { int k = treat_as_empty (j).string_value ().size (); if (k > treat_as_empty_len) treat_as_empty_len = k; } } if (invalid) error ("%s: TreatAsEmpty must be either a string or cell array of one or two strings", who.c_str ()); // FIXME: Ensure none is a prefix of a later one. Sort by length? } else if (param == "collectoutput") { collect_output = args(i+1).xbool_value ("%s: CollectOutput must be logical or numeric", who.c_str ()); } else if (param == "emptyvalue") { empty_value = args(i+1).xscalar_value ("%s: EmptyValue must be numeric", who.c_str ()); } else if (param == "headerlines") { header_lines = args(i+1).xscalar_value ("%s: HeaderLines must be numeric", who.c_str ()); } else if (param == "bufsize") { buffer_size = args(i+1).xscalar_value ("%s: BufSize must be numeric", who.c_str ()); } else if (param == "multipledelimsasone") { multiple_delims_as_one = args(i+1).xbool_value ("%s: MultipleDelimsAsOne must be logical or numeric", who.c_str ()); } else if (param == "returnonerror") { return_on_error = args(i+1).xbool_value ("%s: ReturnOnError must be logical or numeric", who.c_str ()); } else if (param == "whitespace") { whitespace = args(i+1).xstring_value ("%s: Whitespace must be a character string", who.c_str ()); } else if (param == "expchars") { exp_chars = args(i+1).xstring_value ("%s: ExpChars must be a character string", who.c_str ()); default_exp = false; } else if (param == "endofline") { bool valid = true; std::string s = args(i+1).xstring_value ("%s: EndOfLine must be at most one character or '\\r\\n'", who.c_str ()); if (args(i+1).is_sq_string ()) s = do_string_escapes (s); int l = s.length (); if (l == 0) eol1 = eol2 = -2; else if (l == 1) eol1 = eol2 = s.c_str ()[0]; else if (l == 2) { eol1 = s.c_str ()[0]; eol2 = s.c_str ()[1]; if (eol1 != '\r' || eol2 != '\n') // Why limit it? valid = false; } else valid = false; if (! valid) error ("%s: EndOfLine must be at most one character or '\\r\\n'", who.c_str ()); } else error ("%s: unrecognized option '%s'", who.c_str (), param.c_str ()); } whitespace_table = std::string (256, '\0'); for (unsigned int i = 0; i < whitespace.length (); i++) whitespace_table[whitespace[i]] = '1'; // For Matlab compatibility, add 0x20 to whitespace, unless // whitespace is explicitly ignored. if (! (whitespace.empty () && fmt_list.has_string)) whitespace_table[' '] = '1'; // Create look-up table of delimiters, based on 'delimiter' delim_table = std::string (256, '\0'); if (eol1 >= 0 && eol1 < 256) delim_table[eol1] = '1'; // EOL is always a delimiter if (eol2 >= 0 && eol2 < 256) delim_table[eol2] = '1'; // EOL is always a delimiter if (! have_delims) for (unsigned int i = 0; i < 256; i++) { if (isspace (i)) delim_table[i] = '1'; } else for (unsigned int i = 0; i < delims.length (); i++) delim_table[delims[i]] = '1'; } // Skip comments, and characters specified by the "Whitespace" option. // If EOLstop == true, don't skip end of line. int textscan::skip_whitespace (delimited_stream& is, bool EOLstop) { int c1 = std::istream::traits_type::eof (); bool found_comment = false; do { found_comment = false; int prev = -1; while (is && (c1 = is.get_undelim ()) != std::istream::traits_type::eof () && ( ( (c1 == eol1 || c1 == eol2) && ++lines && ! EOLstop) || isspace (c1))) { if (prev == eol1 && eol1 != eol2 && c1 == eol2) lines--; prev = c1; } if (c1 == comment_char) // see if we match an open comment { // save stream state in case we have to restore it char *pos = is.tellg (); std::ios::iostate state = is.rdstate (); std::string tmp (comment_len, '\0'); char *look = is.read (&tmp[0], comment_len-1, pos); // already read first char if (is && ! strncmp (comment_style(0).string_value ().substr (1) .c_str (), look, comment_len-1)) { found_comment = true; std::string dummy; if (comment_style.numel () == 1) // skip to end of line { std::string eol (3, '\0'); eol[0] = eol1; eol[1] = eol2; scan_caret (is, eol, dummy); c1 = is.get_undelim (); if (c1 == eol1 && eol1 != eol2 && is.peek_undelim () == eol2) is.get_undelim (); lines++; } else // matching pair { std::string end_c = comment_style(1).string_value (); // last char of end-comment sequence std::string last = end_c.substr (end_c.size () - 1); std::string may_match (""); do { // find sequence ending with last char scan_caret (is, last, dummy); is.get_undelim (); // (read LAST itself) may_match = may_match + dummy + last; if (may_match.length () > end_c.length ()) { size_t start = may_match.length () - end_c.length (); may_match = may_match.substr (start); } } while (may_match != end_c && is && ! is.eof ()); } } else // wasn't really a comment; restore state { is.clear (state); is.seekg (pos); } } } while (found_comment); if (c1 != std::istream::traits_type::eof ()) is.putback (c1); return c1; } // See if the next few characters match one of the strings in target. // For efficiency, MAX_LEN is the cached longest length of any target. // Return -1 if none is found, or the index of the match. int textscan::lookahead (delimited_stream& is, const Cell& targets, int max_len, bool case_sensitive) const { // target strings may be different sizes. // Read ahead longest, put it all back, then re-read the string // that matches. char *pos = is.tellg (); std::string tmp (max_len, '\0'); char *look = is.read (&tmp[0], tmp.size (), pos); is.clear (); is.seekg (pos); // reset to position before look-ahead // FIXME: pos may be corrupted by is.read int i; int (*compare)(const char *, const char *, size_t); compare = case_sensitive ? strncmp : strncasecmp; for (i = 0; i < targets.numel (); i++) { std::string s = targets (i).string_value (); if (! (*compare) (s.c_str (), look, s.size ())) { is.read (&tmp[0], s.size (), pos); // read just the right amount break; } } if (i == targets.numel ()) i = -1; return i; } // Skip delimiters -- multiple if MultipleDelimsAsOne specified. int textscan::skip_delim (delimited_stream& is) { int c1 = skip_whitespace (is, true); // 'true': stop once EOL is read if (delim_list.numel () == 0) // single character delimiter { if (is_delim (c1) || c1 == eol1 || c1 == eol2) { is.get (); if (c1 == eol1 && is.peek_undelim () == eol2) is.get_undelim (); // if \r\n, skip the \n too. if (multiple_delims_as_one) { int prev = -1; // skip multiple delims. // Increment lines for each end-of-line seen; for \r\n, decrement while (is && ((c1 = is.get_undelim ()) != std::istream::traits_type::eof ()) && (((c1 == eol1 || c1 == eol2) && ++lines) || isspace (c1) || is_delim (c1))) { if (prev == eol1 && eol1 != eol2 && c1 == eol2) lines--; prev = c1; } if (c1 != std::istream::traits_type::eof ()) is.putback (c1); } } } else // multi-character delimiter { int first_match; if (c1 == eol1 || c1 == eol2 || (-1 != (first_match = lookahead (is, delim_list, delim_len)))) { if (c1 == eol1) { is.get_undelim (); if (is.peek_undelim () == eol2) is.get_undelim (); } else if (c1 == eol2) { is.get_undelim (); } if (multiple_delims_as_one) { int prev = -1; // skip multiple delims. // Increment lines for each end-of-line seen; for \r\n, decrement while (is && ((c1 = skip_whitespace (is, true)) != std::istream::traits_type::eof ()) && (((c1 == eol1 || c1 == eol2) && ++lines) || -1 != lookahead (is, delim_list, delim_len))) { if (prev == eol1 && eol1 != eol2 && c1 == eol2) lines--; prev = c1; } } } } return c1; } // Read in as much of the input as coincides with the literal in the // format string. Return "true" if the entire literal is matched, else // false (and set failbit). bool textscan::match_literal (delimited_stream& is, const textscan_format_elt& fmt) { // "false" -> treat EOL as normal space // since a delimiter at the start of a line is a mismatch, not empty field skip_whitespace (is, false); for (unsigned int i = 0; i < fmt.width; i++) { int ch = is.get_undelim (); if (ch != fmt.text[i]) { if (ch != std::istream::traits_type::eof ()) is.putback (ch); is.setstate (std::ios::failbit); return false; } } return true; } } void octave_base_stream::error (const std::string& msg) { fail = true; errmsg = msg; } void octave_base_stream::error (const std::string& who, const std::string& msg) { fail = true; errmsg = who + ": " + msg; } void octave_base_stream::clear (void) { fail = false; errmsg = ""; } void octave_base_stream::clearerr (void) { std::istream *is = input_stream (); std::ostream *os = output_stream (); if (is) is->clear (); if (os) os->clear (); } // Functions that are defined for all input streams (input streams // are those that define is). std::string octave_base_stream::do_gets (octave_idx_type max_len, bool& err, bool strip_newline, const std::string& who) { if (octave::application::interactive () && file_number () == 0) ::error ("%s: unable to read from stdin while running interactively", who.c_str ()); std::string retval; err = false; std::istream *isp = input_stream (); if (! isp) { err = true; invalid_operation (who, "reading"); } else { std::istream& is = *isp; std::ostringstream buf; int c = 0; int char_count = 0; if (max_len != 0) { while (is && (c = is.get ()) != std::istream::traits_type::eof ()) { char_count++; // Handle CRLF, CR, or LF as line ending. if (c == '\r') { if (! strip_newline) buf << static_cast<char> (c); c = is.get (); if (c != std::istream::traits_type::eof ()) { if (c == '\n') { char_count++; if (! strip_newline) buf << static_cast<char> (c); } else is.putback (c); } break; } else if (c == '\n') { if (! strip_newline) buf << static_cast<char> (c); break; } else buf << static_cast<char> (c); if (max_len > 0 && char_count == max_len) break; } } if (! is.eof () && char_count > 0) { // GAGME. Matlab seems to check for EOF even if the last character // in a file is a newline character. This is NOT what the // corresponding C-library functions do. int disgusting_compatibility_hack = is.get (); if (! is.eof ()) is.putback (disgusting_compatibility_hack); } if (is.good () || (is.eof () && char_count > 0)) retval = buf.str (); else { err = true; if (is.eof () && char_count == 0) error (who, "at end of file"); else error (who, "read error"); } } return retval; } std::string octave_base_stream::getl (octave_idx_type max_len, bool& err, const std::string& who) { return do_gets (max_len, err, true, who); } std::string octave_base_stream::gets (octave_idx_type max_len, bool& err, const std::string& who) { return do_gets (max_len, err, false, who); } off_t octave_base_stream::skipl (off_t num, bool& err, const std::string& who) { if (octave::application::interactive () && file_number () == 0) ::error ("%s: unable to read from stdin while running interactively", who.c_str ()); off_t cnt = -1; err = false; std::istream *isp = input_stream (); if (! isp) { err = true; invalid_operation (who, "reading"); } else { std::istream& is = *isp; int c = 0; int lastc = -1; cnt = 0; while (is && (c = is.get ()) != std::istream::traits_type::eof ()) { // Handle CRLF, CR, or LF as line ending. if (c == '\r' || (c == '\n' && lastc != '\r')) { if (++cnt == num) break; } lastc = c; } // Maybe eat the following \n if \r was just met. if (c == '\r' && is.peek () == '\n') is.get (); if (is.bad ()) { err = true; error (who, "read error"); } if (err) cnt = -1; } return cnt; } template <typename T> std::istream& octave_scan_1 (std::istream& is, const scanf_format_elt& fmt, T* valptr) { T value = T (); switch (fmt.type) { case 'o': is >> std::oct >> value >> std::dec; break; case 'x': is >> std::hex >> value >> std::dec; break; case 'i': { int c1 = std::istream::traits_type::eof (); while (is && (c1 = is.get ()) != std::istream::traits_type::eof () && isspace (c1)) ; // skip whitespace if (c1 != std::istream::traits_type::eof ()) { if (c1 == '0') { int c2 = is.peek (); if (c2 == 'x' || c2 == 'X') { is.ignore (); if (std::isxdigit (is.peek ())) is >> std::hex >> value >> std::dec; else value = 0; } else { if (c2 == '0' || c2 == '1' || c2 == '2' || c2 == '3' || c2 == '4' || c2 == '5' || c2 == '6' || c2 == '7') is >> std::oct >> value >> std::dec; else if (c2 == '8' || c2 == '9') { // FIXME: Would like to set error state on octave // stream. See bug #46493. But only std::istream is // input to fcn. // error ("internal failure to match octal format"); value = 0; } else value = 0; } } else { is.putback (c1); is >> value; } } } break; default: is >> value; break; } // If conversion produces an integer that overflows, failbit is set but // value is non-zero. We want to treat this case as success, so clear // failbit from the stream state to keep going. // FIXME: Maybe set error state on octave stream as above? Matlab does // *not* indicate an error message on overflow. if ((is.rdstate () & std::ios::failbit) && value != T ()) is.clear (is.rdstate () & ~std::ios::failbit); // Only copy the converted value if the stream is in a state where we // want to continue reading. if (! (is.rdstate () & std::ios::failbit)) *valptr = value; return is; } template <typename T> std::istream& octave_scan (std::istream& is, const scanf_format_elt& fmt, T* valptr) { if (fmt.width) { // Limit input to fmt.width characters by reading into a // temporary stringstream buffer. std::string tmp; is.width (fmt.width); is >> tmp; std::istringstream ss (tmp); octave_scan_1 (ss, fmt, valptr); } else octave_scan_1 (is, fmt, valptr); return is; } // Note that this specialization is only used for reading characters, not // character strings. See BEGIN_S_CONVERSION for details. template <> std::istream& octave_scan<> (std::istream& is, const scanf_format_elt& /* fmt */, char* valptr) { return is >> valptr; } template <> std::istream& octave_scan<> (std::istream& is, const scanf_format_elt& fmt, double* valptr) { double& ref = *valptr; switch (fmt.type) { case 'e': case 'f': case 'g': { int c1 = std::istream::traits_type::eof (); while (is && (c1 = is.get ()) != std::istream::traits_type::eof () && isspace (c1)) ; // skip whitespace if (c1 != std::istream::traits_type::eof ()) { is.putback (c1); ref = octave_read_value<double> (is); } } break; default: panic_impossible (); break; } return is; } template <typename T> void do_scanf_conv (std::istream& is, const scanf_format_elt& fmt, T valptr, Matrix& mval, double *data, octave_idx_type& idx, octave_idx_type& conversion_count, octave_idx_type nr, octave_idx_type max_size, bool discard) { octave_scan (is, fmt, valptr); if (! is) return; if (idx == max_size && ! discard) { max_size *= 2; if (nr > 0) mval.resize (nr, max_size / nr, 0.0); else mval.resize (max_size, 1, 0.0); data = mval.fortran_vec (); } if (! discard) { conversion_count++; data[idx++] = *(valptr); } } template void do_scanf_conv (std::istream&, const scanf_format_elt&, double*, Matrix&, double*, octave_idx_type&, octave_idx_type&, octave_idx_type, octave_idx_type, bool); #define DO_WHITESPACE_CONVERSION() \ do \ { \ int c = std::istream::traits_type::eof (); \ \ while (is && (c = is.get ()) != std::istream::traits_type::eof () \ && isspace (c)) \ { /* skip whitespace */ } \ \ if (c != std::istream::traits_type::eof ()) \ is.putback (c); \ } \ while (0) #define DO_LITERAL_CONVERSION() \ do \ { \ int c = std::istream::traits_type::eof (); \ \ int n = strlen (fmt); \ int i = 0; \ \ while (i < n && is && (c = is.get ()) != std::istream::traits_type::eof ()) \ { \ if (c == static_cast<unsigned char> (fmt[i])) \ { \ i++; \ continue; \ } \ else \ { \ is.putback (c); \ break; \ } \ } \ \ if (i != n) \ is.setstate (std::ios::failbit); \ } \ while (0) #define DO_PCT_CONVERSION() \ do \ { \ int c = is.get (); \ \ if (c != std::istream::traits_type::eof ()) \ { \ if (c != '%') \ { \ is.putback (c); \ is.setstate (std::ios::failbit); \ } \ } \ else \ is.setstate (std::ios::failbit); \ } \ while (0) #define BEGIN_C_CONVERSION() \ is.unsetf (std::ios::skipws); \ \ int width = elt->width ? elt->width : 1; \ \ std::string tmp (width, '\0'); \ \ int c = std::istream::traits_type::eof (); \ int n = 0; \ \ while (is && n < width \ && (c = is.get ()) != std::istream::traits_type::eof ()) \ tmp[n++] = static_cast<char> (c); \ \ if (n > 0 && c == std::istream::traits_type::eof ()) \ is.clear (); \ \ tmp.resize (n) // For a '%s' format, skip initial whitespace and then read until the // next whitespace character or until WIDTH characters have been read. #define BEGIN_S_CONVERSION() \ int width = elt->width; \ \ std::string tmp; \ \ do \ { \ if (width) \ { \ tmp = std::string (width, '\0'); \ \ int c = std::istream::traits_type::eof (); \ \ int n = 0; \ \ while (is && (c = is.get ()) != std::istream::traits_type::eof ()) \ { \ if (! isspace (c)) \ { \ tmp[n++] = static_cast<char> (c); \ break; \ } \ } \ \ while (is && n < width \ && (c = is.get ()) != std::istream::traits_type::eof ()) \ { \ if (isspace (c)) \ { \ is.putback (c); \ break; \ } \ else \ tmp[n++] = static_cast<char> (c); \ } \ \ if (n > 0 && c == std::istream::traits_type::eof ()) \ is.clear (); \ \ tmp.resize (n); \ } \ else \ { \ is >> std::ws >> tmp; \ } \ } \ while (0) // This format must match a nonempty sequence of characters. #define BEGIN_CHAR_CLASS_CONVERSION() \ int width = elt->width; \ \ std::string tmp; \ \ do \ { \ if (! width) \ width = std::numeric_limits<int>::max (); \ \ std::ostringstream buf; \ \ std::string char_class = elt->char_class; \ \ int c = std::istream::traits_type::eof (); \ \ if (elt->type == '[') \ { \ int chars_read = 0; \ while (is && chars_read++ < width \ && (c = is.get ()) != std::istream::traits_type::eof () \ && char_class.find (c) != std::string::npos) \ buf << static_cast<char> (c); \ } \ else \ { \ int chars_read = 0; \ while (is && chars_read++ < width \ && (c = is.get ()) != std::istream::traits_type::eof () \ && char_class.find (c) == std::string::npos) \ buf << static_cast<char> (c); \ } \ \ if (width == std::numeric_limits<int>::max () \ && c != std::istream::traits_type::eof ()) \ is.putback (c); \ \ tmp = buf.str (); \ \ if (tmp.empty ()) \ is.setstate (std::ios::failbit); \ else if (c == std::istream::traits_type::eof ()) \ is.clear (); \ \ } \ while (0) #define FINISH_CHARACTER_CONVERSION() \ do \ { \ width = tmp.length (); \ \ if (is) \ { \ int i = 0; \ \ if (! discard) \ { \ conversion_count++; \ \ while (i < width) \ { \ if (data_index == max_size) \ { \ max_size *= 2; \ \ if (all_char_conv) \ { \ if (one_elt_size_spec) \ mval.resize (1, max_size, 0.0); \ else if (nr > 0) \ mval.resize (nr, max_size / nr, 0.0); \ else \ panic_impossible (); \ } \ else if (nr > 0) \ mval.resize (nr, max_size / nr, 0.0); \ else \ mval.resize (max_size, 1, 0.0); \ \ data = mval.fortran_vec (); \ } \ \ data[data_index++] = tmp[i++]; \ } \ } \ } \ } \ while (0) octave_value octave_base_stream::do_scanf (scanf_format_list& fmt_list, octave_idx_type nr, octave_idx_type nc, bool one_elt_size_spec, octave_idx_type& conversion_count, const std::string& who) { if (octave::application::interactive () && file_number () == 0) ::error ("%s: unable to read from stdin while running interactively", who.c_str ()); octave_value retval = Matrix (); conversion_count = 0; octave_idx_type nconv = fmt_list.num_conversions (); octave_idx_type data_index = 0; if (nr == 0 || nc == 0) { if (one_elt_size_spec) nc = 0; return Matrix (nr, nc, 0.0); } std::istream *isp = input_stream (); bool all_char_conv = fmt_list.all_character_conversions (); Matrix mval; double *data = 0; octave_idx_type max_size = 0; octave_idx_type max_conv = 0; octave_idx_type final_nr = 0; octave_idx_type final_nc = 0; if (all_char_conv) { // Any of these could be resized later (if we have %s conversions, // we may read more than one element for each conversion). if (one_elt_size_spec) { max_size = 512; mval.resize (1, max_size, 0.0); if (nr > 0) max_conv = nr; } else if (nr > 0) { if (nc > 0) { mval.resize (nr, nc, 0.0); max_size = max_conv = nr * nc; } else { mval.resize (nr, 32, 0.0); max_size = nr * 32; } } else panic_impossible (); } else if (nr > 0) { if (nc > 0) { // Will not resize later. mval.resize (nr, nc, 0.0); max_size = nr * nc; max_conv = max_size; } else { // Maybe resize later. mval.resize (nr, 32, 0.0); max_size = nr * 32; } } else { // Maybe resize later. mval.resize (32, 1, 0.0); max_size = 32; } data = mval.fortran_vec (); if (isp) { std::istream& is = *isp; const scanf_format_elt *elt = fmt_list.first (); std::ios::fmtflags flags = is.flags (); octave_idx_type trips = 0; octave_idx_type num_fmt_elts = fmt_list.length (); for (;;) { octave_quit (); if (elt) { if (elt->type == scanf_format_elt::null || (! (elt->type == scanf_format_elt::whitespace_conversion || elt->type == scanf_format_elt::literal_conversion || elt->type == '%') && max_conv > 0 && conversion_count == max_conv)) { // We are done, either because we have reached the end of the // format string and are not cycling through the format again // or because we've converted all the values that have been // requested and the next format element is a conversion. // Determine final array size and exit. if (all_char_conv && one_elt_size_spec) { final_nr = 1; final_nc = data_index; } else { final_nr = nr; final_nc = (data_index - 1) / nr + 1; } break; } else if (data_index == max_size) { max_size *= 2; if (all_char_conv) { if (one_elt_size_spec) mval.resize (1, max_size, 0.0); else if (nr > 0) mval.resize (nr, max_size / nr, 0.0); else panic_impossible (); } else if (nr > 0) mval.resize (nr, max_size / nr, 0.0); else mval.resize (max_size, 1, 0.0); data = mval.fortran_vec (); } const char *fmt = elt->text; bool discard = elt->discard; switch (elt->type) { case scanf_format_elt::whitespace_conversion: DO_WHITESPACE_CONVERSION (); break; case scanf_format_elt::literal_conversion: DO_LITERAL_CONVERSION (); break; case '%': DO_PCT_CONVERSION (); break; case 'd': case 'i': { switch (elt->modifier) { case 'h': { int16_t tmp; do_scanf_conv (is, *elt, &tmp, mval, data, data_index, conversion_count, nr, max_size, discard); } break; case 'l': { int64_t tmp; do_scanf_conv (is, *elt, &tmp, mval, data, data_index, conversion_count, nr, max_size, discard); } break; default: { int32_t tmp; do_scanf_conv (is, *elt, &tmp, mval, data, data_index, conversion_count, nr, max_size, discard); } break; } } break; case 'o': case 'u': case 'x': { switch (elt->modifier) { case 'h': { uint16_t tmp; do_scanf_conv (is, *elt, &tmp, mval, data, data_index, conversion_count, nr, max_size, discard); } break; case 'l': { uint64_t tmp; do_scanf_conv (is, *elt, &tmp, mval, data, data_index, conversion_count, nr, max_size, discard); } break; default: { uint32_t tmp; do_scanf_conv (is, *elt, &tmp, mval, data, data_index, conversion_count, nr, max_size, discard); } break; } } break; case 'e': case 'f': case 'g': { double tmp; do_scanf_conv (is, *elt, &tmp, mval, data, data_index, conversion_count, nr, max_size, discard); } break; case 'c': { BEGIN_C_CONVERSION (); FINISH_CHARACTER_CONVERSION (); is.setf (flags); } break; case 's': { BEGIN_S_CONVERSION (); FINISH_CHARACTER_CONVERSION (); } break; case '[': case '^': { BEGIN_CHAR_CLASS_CONVERSION (); FINISH_CHARACTER_CONVERSION (); } break; case 'p': error ("%s: unsupported format specifier", who.c_str ()); break; default: error ("%s: internal format error", who.c_str ()); break; } if (! ok ()) { break; } else if (! is) { if (all_char_conv) { if (one_elt_size_spec) { final_nr = 1; final_nc = data_index; } else if (data_index > nr) { final_nr = nr; final_nc = (data_index - 1) / nr + 1; } else { final_nr = data_index; final_nc = 1; } } else if (nr > 0) { if (data_index > nr) { final_nr = nr; final_nc = (data_index - 1) / nr + 1; } else { final_nr = data_index; final_nc = 1; } } else { final_nr = data_index; final_nc = 1; } // If it looks like we have a matching failure, then // reset the failbit in the stream state. if (is.rdstate () & std::ios::failbit) is.clear (is.rdstate () & (~std::ios::failbit)); // FIXME: is this the right thing to do? if (octave::application::interactive () && ! octave::application::forced_interactive () && name () == "stdin") { is.clear (); // Skip to end of line. bool err; do_gets (-1, err, false, who); } break; } } else { error ("%s: internal format error", who.c_str ()); break; } if (nconv == 0 && ++trips == num_fmt_elts) { if (all_char_conv && one_elt_size_spec) { final_nr = 1; final_nc = data_index; } else { final_nr = nr; final_nc = (data_index - 1) / nr + 1; } break; } else { // Cycle through the format list more than once if we have some // conversions to make and we haven't reached the limit on the // number of values to convert (possibly because there is no // specified limit). elt = fmt_list.next (nconv > 0 && (max_conv == 0 || conversion_count < max_conv)); } } } if (ok ()) { mval.resize (final_nr, final_nc, 0.0); retval = mval; if (all_char_conv) retval = retval.convert_to_str (false, true); } return retval; } octave_value octave_base_stream::scanf (const std::string& fmt, const Array<double>& size, octave_idx_type& conversion_count, const std::string& who) { octave_value retval = Matrix (); conversion_count = 0; std::istream *isp = input_stream (); if (! isp) invalid_operation (who, "reading"); else { scanf_format_list fmt_list (fmt); if (fmt_list.num_conversions () == -1) ::error ("%s: invalid format specified", who.c_str ()); octave_idx_type nr = -1; octave_idx_type nc = -1; bool one_elt_size_spec; get_size (size, nr, nc, one_elt_size_spec, who); retval = do_scanf (fmt_list, nr, nc, one_elt_size_spec, conversion_count, who); } return retval; } bool octave_base_stream::do_oscanf (const scanf_format_elt *elt, octave_value& retval, const std::string& who) { std::istream *isp = input_stream (); if (! isp) return false; bool quit = false; std::istream& is = *isp; std::ios::fmtflags flags = is.flags (); if (elt) { const char *fmt = elt->text; bool discard = elt->discard; switch (elt->type) { case scanf_format_elt::whitespace_conversion: DO_WHITESPACE_CONVERSION (); break; case scanf_format_elt::literal_conversion: DO_LITERAL_CONVERSION (); break; case '%': { DO_PCT_CONVERSION (); if (! is) quit = true; } break; case 'd': case 'i': { int tmp; if (octave_scan (is, *elt, &tmp)) { if (! discard) retval = tmp; } else quit = true; } break; case 'o': case 'u': case 'x': { long int tmp; if (octave_scan (is, *elt, &tmp)) { if (! discard) retval = tmp; } else quit = true; } break; case 'e': case 'f': case 'g': { double tmp; if (octave_scan (is, *elt, &tmp)) { if (! discard) retval = tmp; } else quit = true; } break; case 'c': { BEGIN_C_CONVERSION (); if (! discard) retval = tmp; if (! is) quit = true; is.setf (flags); } break; case 's': { BEGIN_S_CONVERSION (); if (! discard) retval = tmp; if (! is) quit = true; } break; case '[': case '^': { BEGIN_CHAR_CLASS_CONVERSION (); if (! discard) retval = tmp; if (! is) quit = true; } break; case 'p': error ("%s: unsupported format specifier", who.c_str ()); break; default: error ("%s: internal format error", who.c_str ()); break; } } if (ok () && is.fail ()) { error ("%s: read error", who.c_str ()); // FIXME: is this the right thing to do? if (octave::application::interactive () && ! octave::application::forced_interactive () && name () == "stdin") { // Skip to end of line. bool err; do_gets (-1, err, false, who); } } return quit; } octave_value_list octave_base_stream::oscanf (const std::string& fmt, const std::string& who) { octave_value_list retval; std::istream *isp = input_stream (); if (! isp) invalid_operation (who, "reading"); else { std::istream& is = *isp; scanf_format_list fmt_list (fmt); octave_idx_type nconv = fmt_list.num_conversions (); if (nconv == -1) ::error ("%s: invalid format specified", who.c_str ()); is.clear (); octave_idx_type len = fmt_list.length (); retval.resize (nconv+2, Matrix ()); const scanf_format_elt *elt = fmt_list.first (); int num_values = 0; bool quit = false; for (octave_idx_type i = 0; i < len; i++) { octave_value tmp; quit = do_oscanf (elt, tmp, who); if (quit) break; else { if (tmp.is_defined ()) retval(num_values++) = tmp; if (! ok ()) break; elt = fmt_list.next (nconv > 0); } } retval(nconv) = num_values; int err_num; retval(nconv+1) = error (false, err_num); if (! quit) { // Pick up any trailing stuff. if (ok () && len > nconv) { octave_value tmp; elt = fmt_list.next (); do_oscanf (elt, tmp, who); } } } return retval; } octave_value octave_base_stream::do_textscan (const std::string& fmt, octave_idx_type ntimes, const octave_value_list& options, const std::string& who, octave_idx_type& read_count) { if (octave::application::interactive () && file_number () == 0) ::error ("%s: unable to read from stdin while running interactively", who.c_str ()); octave_value retval = Cell (dim_vector (1, 1), Matrix (0, 1)); std::istream *isp = input_stream (); if (! isp) invalid_operation (who, "reading"); else { octave::textscan scanner (who); retval = scanner.scan (*isp, fmt, ntimes, options, read_count); } return retval; } // Functions that are defined for all output streams // (output streams are those that define os). int octave_base_stream::flush (void) { int retval = -1; std::ostream *os = output_stream (); if (! os) invalid_operation ("fflush", "writing"); else { os->flush (); if (os->good ()) retval = 0; } return retval; } class printf_value_cache { public: enum state { ok, conversion_error }; printf_value_cache (const octave_value_list& args, const std::string& who) : values (args), val_idx (0), elt_idx (0), n_vals (values.length ()), n_elts (0), have_data (false), curr_state (ok) { for (octave_idx_type i = 0; i < values.length (); i++) { octave_value val = values(i); if (val.is_map () || val.is_cell () || val.is_object ()) err_wrong_type_arg (who, val); } } ~printf_value_cache (void) { } // Get the current value as a double and advance the internal pointer. octave_value get_next_value (char type = 0); // Get the current value as an int and advance the internal pointer. int int_value (void); operator bool () const { return (curr_state == ok); } bool exhausted (void) { return (val_idx >= n_vals); } private: const octave_value_list values; int val_idx; int elt_idx; int n_vals; int n_elts; bool have_data; octave_value curr_val; state curr_state; // Must create value cache with values! printf_value_cache (void); // No copying! printf_value_cache (const printf_value_cache&); printf_value_cache& operator = (const printf_value_cache&); }; octave_value printf_value_cache::get_next_value (char type) { octave_value retval; if (exhausted ()) curr_state = conversion_error; while (! exhausted ()) { if (! have_data) { curr_val = values (val_idx); elt_idx = 0; n_elts = curr_val.numel (); have_data = true; } if (elt_idx < n_elts) { if (type == 's') { if (curr_val.is_string ()) { dim_vector dv (1, curr_val.numel ()); octave_value tmp = curr_val.reshape (dv); std::string sval = tmp.string_value (); retval = sval.substr (elt_idx); // We've consumed the rest of the value. elt_idx = n_elts; } else { // Convert to character string while values are // integers in the range [0 : char max] const NDArray val = curr_val.array_value (); octave_idx_type idx = elt_idx; for (; idx < n_elts; idx++) { double dval = val(idx); if (octave::math::x_nint (dval) != dval || dval < 0 || dval > 255) break; } octave_idx_type n = idx - elt_idx; if (n > 0) { std::string sval (n, '\0'); for (octave_idx_type i = 0; i < n; i++) sval[i] = val(elt_idx++); retval = sval; } else retval = curr_val.fast_elem_extract (elt_idx++); } } else { retval = curr_val.fast_elem_extract (elt_idx++); if (type == 'c' && ! retval.is_string ()) { double dval = retval.double_value (); if (octave::math::x_nint (dval) == dval && dval >= 0 && dval < 256) retval = static_cast<char> (dval); } } if (elt_idx >= n_elts) { elt_idx = 0; val_idx++; have_data = false; } break; } else { val_idx++; have_data = false; if (n_elts == 0) { if (elt_idx == 0 && (type == 's' || type == 'c')) { retval = ""; break; } if (exhausted ()) curr_state = conversion_error; } } } return retval; } int printf_value_cache::int_value (void) { int retval = 0; octave_value val = get_next_value (); double dval = val.double_value (true); if (octave::math::x_nint (dval) == dval) retval = octave::math::nint (dval); else curr_state = conversion_error; return retval; } // Ugh again and again. template <typename T> int do_printf_conv (std::ostream& os, const char *fmt, int nsa, int sa_1, int sa_2, T arg, const std::string& who) { int retval = 0; switch (nsa) { case 2: retval = octave_format (os, fmt, sa_1, sa_2, arg); break; case 1: retval = octave_format (os, fmt, sa_1, arg); break; case 0: retval = octave_format (os, fmt, arg); break; default: ::error ("%s: internal error handling format", who.c_str ()); break; } return retval; } static size_t do_printf_string (std::ostream& os, const printf_format_elt *elt, int nsa, int sa_1, int sa_2, const std::string& arg, const std::string& who) { if (nsa > 2) ::error ("%s: internal error handling format", who.c_str ()); std::string flags = elt->flags; bool left = flags.find ('-') != std::string::npos; size_t len = arg.length (); size_t fw = nsa > 0 ? sa_1 : (elt->fw == -1 ? len : elt->fw); size_t prec = nsa > 1 ? sa_2 : (elt->prec == -1 ? len : elt->prec); os << std::setw (fw) << (left ? std::left : std::right) << (prec < len ? arg.substr (0, prec) : arg); return len > fw ? len : fw; } static bool is_nan_or_inf (const octave_value& val) { octave_value ov_isnan = val.isnan (); octave_value ov_isinf = val.isinf (); return (ov_isnan.is_true () || ov_isinf.is_true ()); } static bool ok_for_signed_int_conv (const octave_value& val) { uint64_t limit = std::numeric_limits<int64_t>::max (); if (val.is_string ()) return true; else if (val.is_integer_type ()) { if (val.is_uint64_type ()) { octave_uint64 ival = val.uint64_scalar_value (); if (ival.value () <= limit) return true; } else return true; } else { double dval = val.double_value (true); if (dval == octave::math::round (dval) && dval <= limit) return true; } return false; } static bool ok_for_unsigned_int_conv (const octave_value& val) { if (val.is_string ()) return true; else if (val.is_integer_type ()) { // Easier than dispatching here... octave_value ov_is_ge_zero = do_binary_op (octave_value::op_ge, val, octave_value (0.0)); return ov_is_ge_zero.is_true (); } else { double dval = val.double_value (true); uint64_t limit = std::numeric_limits<uint64_t>::max (); if (dval == octave::math::round (dval) && dval >= 0 && dval <= limit) return true; } return false; } static std::string switch_to_g_format (const printf_format_elt *elt) { std::string tfmt = elt->text; tfmt.replace (tfmt.rfind (elt->type), 1, "g"); return tfmt; } int octave_base_stream::do_numeric_printf_conv (std::ostream& os, const printf_format_elt *elt, int nsa, int sa_1, int sa_2, const octave_value& val, const std::string& who) { int retval = 0; const char *fmt = elt->text; if (is_nan_or_inf (val)) { double dval = val.double_value (); std::string tfmt = fmt; std::string::size_type i1, i2; tfmt.replace ((i1 = tfmt.rfind (elt->type)), 1, 1, 's'); if ((i2 = tfmt.rfind ('.')) != std::string::npos && i2 < i1) { tfmt.erase (i2, i1-i2); if (elt->prec == -2) nsa--; } const char *tval; if (lo_ieee_isinf (dval)) { if (elt->flags.find ('+') != std::string::npos) tval = (dval < 0 ? "-Inf" : "+Inf"); else tval = (dval < 0 ? "-Inf" : "Inf"); } else { if (elt->flags.find ('+') != std::string::npos) tval = (lo_ieee_is_NA (dval) ? "+NA" : "+NaN"); else tval = (lo_ieee_is_NA (dval) ? "NA" : "NaN"); } retval += do_printf_conv (os, tfmt.c_str (), nsa, sa_1, sa_2, tval, who); } else { static std::string llmod = sizeof (long) == sizeof (int64_t) ? "l" : "ll"; char type = elt->type; switch (type) { case 'd': case 'i': case 'c': if (ok_for_signed_int_conv (val)) { octave_int64 tval = val.int64_scalar_value (); // Insert "long" modifier. std::string tfmt = fmt; tfmt.replace (tfmt.rfind (type), 1, llmod + type); retval += do_printf_conv (os, tfmt.c_str (), nsa, sa_1, sa_2, tval.value (), who); } else { std::string tfmt = switch_to_g_format (elt); double dval = val.double_value (true); retval += do_printf_conv (os, tfmt.c_str (), nsa, sa_1, sa_2, dval, who); } break; case 'o': case 'x': case 'X': case 'u': if (ok_for_unsigned_int_conv (val)) { octave_uint64 tval = val.uint64_scalar_value (); // Insert "long" modifier. std::string tfmt = fmt; tfmt.replace (tfmt.rfind (type), 1, llmod + type); retval += do_printf_conv (os, tfmt.c_str (), nsa, sa_1, sa_2, tval.value (), who); } else { std::string tfmt = switch_to_g_format (elt); double dval = val.double_value (true); retval += do_printf_conv (os, tfmt.c_str (), nsa, sa_1, sa_2, dval, who); } break; case 'f': case 'e': case 'E': case 'g': case 'G': { double dval = val.double_value (true); retval += do_printf_conv (os, fmt, nsa, sa_1, sa_2, dval, who); } break; default: // Note: error is member fcn from octave_base_stream, not ::error. // This error does not halt execution so "return ..." must exist. error ("%s: invalid format specifier", who.c_str ()); return -1; break; } } return retval; } int octave_base_stream::do_printf (printf_format_list& fmt_list, const octave_value_list& args, const std::string& who) { int retval = 0; octave_idx_type nconv = fmt_list.num_conversions (); std::ostream *osp = output_stream (); if (! osp) invalid_operation (who, "writing"); else { std::ostream& os = *osp; const printf_format_elt *elt = fmt_list.first (); printf_value_cache val_cache (args, who); for (;;) { octave_quit (); if (! elt) ::error ("%s: internal error handling format", who.c_str ()); // NSA is the number of 'star' args to convert. int nsa = (elt->fw == -2) + (elt->prec == -2); int sa_1 = 0; int sa_2 = 0; if (nsa > 0) { sa_1 = val_cache.int_value (); if (! val_cache) break; else { if (nsa > 1) { sa_2 = val_cache.int_value (); if (! val_cache) break; } } } if (elt->type == '%') { os << "%"; retval++; } else if (elt->args == 0 && elt->text) { os << elt->text; retval += strlen (elt->text); } else if (elt->type == 's' || elt->type == 'c') { octave_value val = val_cache.get_next_value (elt->type); if (val_cache) { if (val.is_string ()) { std::string sval = val.string_value (); retval += do_printf_string (os, elt, nsa, sa_1, sa_2, sval, who); } else retval += do_numeric_printf_conv (os, elt, nsa, sa_1, sa_2, val, who); } else break; } else { octave_value val = val_cache.get_next_value (); if (val_cache) retval += do_numeric_printf_conv (os, elt, nsa, sa_1, sa_2, val, who); else break; } if (! os) { error ("%s: write error", who.c_str ()); break; } elt = fmt_list.next (nconv > 0 && ! val_cache.exhausted ()); if (! elt || (val_cache.exhausted () && elt->args > 0)) break; } } return retval; } int octave_base_stream::printf (const std::string& fmt, const octave_value_list& args, const std::string& who) { printf_format_list fmt_list (fmt); if (fmt_list.num_conversions () == -1) ::error ("%s: invalid format specified", who.c_str ()); return do_printf (fmt_list, args, who); } int octave_base_stream::puts (const std::string& s, const std::string& who) { int retval = -1; std::ostream *osp = output_stream (); if (! osp) invalid_operation (who, "writing"); else { std::ostream& os = *osp; os << s; if (! os) error ("%s: write error", who.c_str ()); else { // FIXME: why does this seem to be necessary? // Without it, output from a loop like // // for i = 1:100, fputs (stdout, "foo\n"); endfor // // doesn't seem to go to the pager immediately. os.flush (); if (os) retval = 0; else error ("%s: write error", who.c_str ()); } } return retval; } // Return current error message for this stream. std::string octave_base_stream::error (bool clear_err, int& err_num) { err_num = fail ? -1 : 0; std::string tmp = errmsg; if (clear_err) clear (); return tmp; } void octave_base_stream::invalid_operation (const std::string& who, const char *rw) { // Note: This calls the member fcn error, not ::error from error.h. error (who, std::string ("stream not open for ") + rw); } octave_stream::octave_stream (octave_base_stream *bs) : rep (bs) { if (rep) rep->count = 1; } octave_stream::~octave_stream (void) { if (rep && --rep->count == 0) delete rep; } octave_stream::octave_stream (const octave_stream& s) : rep (s.rep) { if (rep) rep->count++; } octave_stream& octave_stream::operator = (const octave_stream& s) { if (rep != s.rep) { if (rep && --rep->count == 0) delete rep; rep = s.rep; if (rep) rep->count++; } return *this; } int octave_stream::flush (void) { int retval = -1; if (stream_ok ()) retval = rep->flush (); return retval; } std::string octave_stream::getl (octave_idx_type max_len, bool& err, const std::string& who) { std::string retval; if (stream_ok ()) retval = rep->getl (max_len, err, who); return retval; } std::string octave_stream::getl (const octave_value& tc_max_len, bool& err, const std::string& who) { err = false; int conv_err = 0; int max_len = -1; if (tc_max_len.is_defined ()) { max_len = convert_to_valid_int (tc_max_len, conv_err); if (conv_err || max_len < 0) { err = true; ::error ("%s: invalid maximum length specified", who.c_str ()); } } return getl (max_len, err, who); } std::string octave_stream::gets (octave_idx_type max_len, bool& err, const std::string& who) { std::string retval; if (stream_ok ()) retval = rep->gets (max_len, err, who); return retval; } std::string octave_stream::gets (const octave_value& tc_max_len, bool& err, const std::string& who) { err = false; int conv_err = 0; int max_len = -1; if (tc_max_len.is_defined ()) { max_len = convert_to_valid_int (tc_max_len, conv_err); if (conv_err || max_len < 0) { err = true; ::error ("%s: invalid maximum length specified", who.c_str ()); } } return gets (max_len, err, who); } off_t octave_stream::skipl (off_t count, bool& err, const std::string& who) { off_t retval = -1; if (stream_ok ()) retval = rep->skipl (count, err, who); return retval; } off_t octave_stream::skipl (const octave_value& tc_count, bool& err, const std::string& who) { err = false; int conv_err = 0; int count = 1; if (tc_count.is_defined ()) { if (tc_count.is_scalar_type () && octave::math::isinf (tc_count.scalar_value ())) count = -1; else { count = convert_to_valid_int (tc_count, conv_err); if (conv_err || count < 0) { err = true; ::error ("%s: invalid number of lines specified", who.c_str ()); } } } return skipl (count, err, who); } int octave_stream::seek (off_t offset, int origin) { int status = -1; if (stream_ok ()) { clearerr (); // Find current position so we can return to it if needed. off_t orig_pos = rep->tell (); // Move to end of file. If successful, find the offset of the end. status = rep->seek (0, SEEK_END); if (status == 0) { off_t eof_pos = rep->tell (); if (origin == SEEK_CUR) { // Move back to original position, otherwise we will be seeking // from the end of file which is probably not the original // location. rep->seek (orig_pos, SEEK_SET); } // Attempt to move to desired position; may be outside bounds of // existing file. status = rep->seek (offset, origin); if (status == 0) { // Where are we after moving to desired position? off_t desired_pos = rep->tell (); // I don't think save_pos can be less than zero, // but we'll check anyway... if (desired_pos > eof_pos || desired_pos < 0) { // Seek outside bounds of file. // Failure should leave position unchanged. rep->seek (orig_pos, SEEK_SET); status = -1; } } else { // Seeking to the desired position failed. // Move back to original position and return failure status. rep->seek (orig_pos, SEEK_SET); status = -1; } } } return status; } int octave_stream::seek (const octave_value& tc_offset, const octave_value& tc_origin) { int retval = -1; // FIXME: should we have octave_value methods that handle off_t explicitly? octave_int64 val = tc_offset.xint64_scalar_value ("fseek: invalid value for offset"); off_t xoffset = val.value (); int conv_err = 0; int origin = SEEK_SET; if (tc_origin.is_string ()) { std::string xorigin = tc_origin.string_value ("fseek: invalid value for origin"); if (xorigin == "bof") origin = SEEK_SET; else if (xorigin == "cof") origin = SEEK_CUR; else if (xorigin == "eof") origin = SEEK_END; else conv_err = -1; } else { int xorigin = convert_to_valid_int (tc_origin, conv_err); if (! conv_err) { if (xorigin == -1) origin = SEEK_SET; else if (xorigin == 0) origin = SEEK_CUR; else if (xorigin == 1) origin = SEEK_END; else conv_err = -1; } } if (conv_err) ::error ("fseek: invalid value for origin"); retval = seek (xoffset, origin); if (retval != 0) // Note: error is member fcn from octave_stream, not ::error. error ("fseek: failed to seek to requested position"); return retval; } off_t octave_stream::tell (void) { off_t retval = -1; if (stream_ok ()) retval = rep->tell (); return retval; } int octave_stream::rewind (void) { return seek (0, SEEK_SET); } bool octave_stream::is_open (void) const { bool retval = false; if (stream_ok ()) retval = rep->is_open (); return retval; } void octave_stream::close (void) { if (stream_ok ()) rep->close (); } // FIXME: maybe these should be defined in lo-ieee.h? template <typename T> static inline bool is_old_NA (T) { return false; } template <> inline bool is_old_NA<double> (double val) { return __lo_ieee_is_old_NA (val); } template <typename T> static inline T replace_old_NA (T val) { return val; } template <> inline double replace_old_NA<double> (double val) { return __lo_ieee_replace_old_NA (val); } template <typename SRC_T, typename DST_T> static octave_value convert_and_copy (std::list<void *>& input_buf_list, octave_idx_type input_buf_elts, octave_idx_type elts_read, octave_idx_type nr, octave_idx_type nc, bool swap, bool do_float_fmt_conv, bool do_NA_conv, octave::mach_info::float_format from_flt_fmt) { typedef typename DST_T::element_type dst_elt_type; DST_T conv (dim_vector (nr, nc)); dst_elt_type *conv_data = conv.fortran_vec (); octave_idx_type j = 0; for (std::list<void *>::const_iterator it = input_buf_list.begin (); it != input_buf_list.end (); it++) { SRC_T *data = static_cast<SRC_T *> (*it); if (swap || do_float_fmt_conv) { if (do_NA_conv) { for (octave_idx_type i = 0; i < input_buf_elts && j < elts_read; i++, j++) { if (swap) swap_bytes<sizeof (SRC_T)> (&data[i]); else if (do_float_fmt_conv) do_float_format_conversion (&data[i], sizeof (SRC_T), 1, from_flt_fmt, octave::mach_info::native_float_format ()); dst_elt_type tmp (data[i]); if (is_old_NA (tmp)) tmp = replace_old_NA (tmp); conv_data[j] = tmp; } } else { for (octave_idx_type i = 0; i < input_buf_elts && j < elts_read; i++, j++) { if (swap) swap_bytes<sizeof (SRC_T)> (&data[i]); else if (do_float_fmt_conv) do_float_format_conversion (&data[i], sizeof (SRC_T), 1, from_flt_fmt, octave::mach_info::native_float_format ()); conv_data[j] = data[i]; } } } else { if (do_NA_conv) { for (octave_idx_type i = 0; i < input_buf_elts && j < elts_read; i++, j++) { dst_elt_type tmp (data[i]); if (is_old_NA (tmp)) tmp = replace_old_NA (tmp); conv_data[j] = tmp; } } else { for (octave_idx_type i = 0; i < input_buf_elts && j < elts_read; i++, j++) conv_data[j] = data[i]; } } delete [] data; } input_buf_list.clear (); for (octave_idx_type i = elts_read; i < nr * nc; i++) conv_data[i] = dst_elt_type (0); return conv; } typedef octave_value (*conv_fptr) (std::list<void *>& input_buf_list, octave_idx_type input_buf_elts, octave_idx_type elts_read, octave_idx_type nr, octave_idx_type nc, bool swap, bool do_float_fmt_conv, bool do_NA_conv, octave::mach_info::float_format from_flt_fmt); #define TABLE_ELT(T, U, V, W) \ conv_fptr_table[oct_data_conv::T][oct_data_conv::U] = convert_and_copy<V, W> #define FILL_TABLE_ROW(T, V) \ TABLE_ELT (T, dt_int8, V, int8NDArray); \ TABLE_ELT (T, dt_uint8, V, uint8NDArray); \ TABLE_ELT (T, dt_int16, V, int16NDArray); \ TABLE_ELT (T, dt_uint16, V, uint16NDArray); \ TABLE_ELT (T, dt_int32, V, int32NDArray); \ TABLE_ELT (T, dt_uint32, V, uint32NDArray); \ TABLE_ELT (T, dt_int64, V, int64NDArray); \ TABLE_ELT (T, dt_uint64, V, uint64NDArray); \ TABLE_ELT (T, dt_single, V, FloatNDArray); \ TABLE_ELT (T, dt_double, V, NDArray); \ TABLE_ELT (T, dt_char, V, charNDArray); \ TABLE_ELT (T, dt_schar, V, charNDArray); \ TABLE_ELT (T, dt_uchar, V, charNDArray); \ TABLE_ELT (T, dt_logical, V, boolNDArray); octave_value octave_stream::finalize_read (std::list<void *>& input_buf_list, octave_idx_type input_buf_elts, octave_idx_type elts_read, octave_idx_type nr, octave_idx_type nc, oct_data_conv::data_type input_type, oct_data_conv::data_type output_type, octave::mach_info::float_format ffmt) { octave_value retval; static bool initialized = false; // Table function pointers for return types x read types. static conv_fptr conv_fptr_table[oct_data_conv::dt_unknown][14]; if (! initialized) { for (int i = 0; i < oct_data_conv::dt_unknown; i++) for (int j = 0; j < 14; j++) conv_fptr_table[i][j] = 0; FILL_TABLE_ROW (dt_int8, int8_t); FILL_TABLE_ROW (dt_uint8, uint8_t); FILL_TABLE_ROW (dt_int16, int16_t); FILL_TABLE_ROW (dt_uint16, uint16_t); FILL_TABLE_ROW (dt_int32, int32_t); FILL_TABLE_ROW (dt_uint32, uint32_t); FILL_TABLE_ROW (dt_int64, int64_t); FILL_TABLE_ROW (dt_uint64, uint64_t); FILL_TABLE_ROW (dt_single, float); FILL_TABLE_ROW (dt_double, double); FILL_TABLE_ROW (dt_char, char); FILL_TABLE_ROW (dt_schar, signed char); FILL_TABLE_ROW (dt_uchar, unsigned char); FILL_TABLE_ROW (dt_logical, bool); initialized = true; } bool swap = false; if (ffmt == octave::mach_info::flt_fmt_unknown) ffmt = float_format (); if (octave::mach_info::words_big_endian ()) swap = (ffmt == octave::mach_info::flt_fmt_ieee_little_endian); else swap = (ffmt == octave::mach_info::flt_fmt_ieee_big_endian); bool do_float_fmt_conv = ((input_type == oct_data_conv::dt_double || input_type == oct_data_conv::dt_single) && ffmt != float_format ()); bool do_NA_conv = (output_type == oct_data_conv::dt_double); switch (output_type) { case oct_data_conv::dt_int8: case oct_data_conv::dt_uint8: case oct_data_conv::dt_int16: case oct_data_conv::dt_uint16: case oct_data_conv::dt_int32: case oct_data_conv::dt_uint32: case oct_data_conv::dt_int64: case oct_data_conv::dt_uint64: case oct_data_conv::dt_single: case oct_data_conv::dt_double: case oct_data_conv::dt_char: case oct_data_conv::dt_schar: case oct_data_conv::dt_uchar: case oct_data_conv::dt_logical: { conv_fptr fptr = conv_fptr_table[input_type][output_type]; retval = fptr (input_buf_list, input_buf_elts, elts_read, nr, nc, swap, do_float_fmt_conv, do_NA_conv, ffmt); } break; default: ::error ("read: invalid type specification"); } return retval; } octave_value octave_stream::read (const Array<double>& size, octave_idx_type block_size, oct_data_conv::data_type input_type, oct_data_conv::data_type output_type, octave_idx_type skip, octave::mach_info::float_format ffmt, octave_idx_type& count) { octave_value retval; octave_idx_type nr = -1; octave_idx_type nc = -1; bool one_elt_size_spec = false; if (! stream_ok ()) return retval; // FIXME: we may eventually want to make this extensible. // FIXME: we need a better way to ensure that this // numbering stays consistent with the order of the elements in the // data_type enum in the oct_data_conv class. // Expose this in a future version? size_t char_count = 0; ptrdiff_t tmp_count = 0; try { get_size (size, nr, nc, one_elt_size_spec, "fread"); } catch (const octave::execution_exception&) { invalid_operation ("fread", "reading"); return retval; } if (one_elt_size_spec) { // If NR == 0, Matlab returns [](0x0). // If NR > 0, the result will be a column vector with the given // number of rows. // If NR < 0, then we have Inf and the result will be a column // vector but we have to wait to see how big NR will be. if (nr == 0) nr = nc = 0; else nc = 1; } else { // Matlab returns [] even if there are two elements in the size // specification and one is nonzero. // If NC < 0 we have [NR, Inf] and we'll wait to decide how big NC // should be. if (nr == 0 || nc == 0) nr = nc = 0; } // FIXME: Ensure that this does not overflow. // Maybe try comparing nr * nc computed in double with // std::numeric_limits<octave_idx_type>::max (); octave_idx_type elts_to_read = nr * nc; bool read_to_eof = elts_to_read < 0; octave_idx_type input_buf_elts = -1; if (skip == 0) { if (read_to_eof) input_buf_elts = 1024 * 1024; else input_buf_elts = elts_to_read; } else input_buf_elts = block_size; octave_idx_type input_elt_size = oct_data_conv::data_type_size (input_type); ptrdiff_t input_buf_size = static_cast<ptrdiff_t> (input_buf_elts) * input_elt_size; assert (input_buf_size >= 0); // Must also work and return correct type object // for 0 elements to read. std::istream *isp = input_stream (); if (! isp) error ("fread: invalid input stream"); else { std::istream& is = *isp; std::list <void *> input_buf_list; while (is && ! is.eof () && (read_to_eof || tmp_count < elts_to_read)) { if (! read_to_eof) { octave_idx_type remaining_elts = elts_to_read - tmp_count; if (remaining_elts < input_buf_elts) input_buf_size = remaining_elts * input_elt_size; } char *input_buf = new char [input_buf_size]; is.read (input_buf, input_buf_size); size_t gcount = is.gcount (); char_count += gcount; octave_idx_type nel = gcount / input_elt_size; tmp_count += nel; input_buf_list.push_back (input_buf); if (is && skip != 0 && nel == block_size) { // Seek to skip. // If skip would move past EOF, position at EOF. off_t orig_pos = tell (); seek (0, SEEK_END); off_t eof_pos = tell (); // Is it possible for this to fail to return us to // the original position? seek (orig_pos, SEEK_SET); off_t remaining = eof_pos - orig_pos; if (remaining < skip) seek (0, SEEK_END); else seek (skip, SEEK_CUR); if (! is) break; } } if (read_to_eof) { if (nc < 0) { nc = tmp_count / nr; if (tmp_count % nr != 0) nc++; } else nr = tmp_count; } else if (tmp_count == 0) { nr = 0; nc = 0; } else if (tmp_count != nr * nc) { if (tmp_count % nr != 0) nc = tmp_count / nr + 1; else nc = tmp_count / nr; if (tmp_count < nr) nr = tmp_count; } if (tmp_count > std::numeric_limits<octave_idx_type>::max ()) error ("fread: number of elements read exceeds max index size"); else count = static_cast<octave_idx_type> (tmp_count); retval = finalize_read (input_buf_list, input_buf_elts, count, nr, nc, input_type, output_type, ffmt); } return retval; } octave_idx_type octave_stream::write (const octave_value& data, octave_idx_type block_size, oct_data_conv::data_type output_type, octave_idx_type skip, octave::mach_info::float_format flt_fmt) { octave_idx_type retval = -1; if (! stream_ok ()) invalid_operation ("fwrite", "writing"); else { if (flt_fmt == octave::mach_info::flt_fmt_unknown) flt_fmt = float_format (); octave_idx_type status = data.write (*this, block_size, output_type, skip, flt_fmt); if (status < 0) error ("fwrite: write error"); else retval = status; } return retval; } template <typename T, typename V> static void convert_chars (const void *data, void *conv_data, octave_idx_type n_elts) { const T *tt_data = static_cast<const T *> (data); V *vt_data = static_cast<V *> (conv_data); for (octave_idx_type i = 0; i < n_elts; i++) vt_data[i] = tt_data[i]; } template <typename T, typename V> static void convert_ints (const T *data, void *conv_data, octave_idx_type n_elts, bool swap) { typedef typename V::val_type val_type; val_type *vt_data = static_cast<val_type *> (conv_data); for (octave_idx_type i = 0; i < n_elts; i++) { // Yes, we want saturation semantics when converting to an integer type. V val (data[i]); vt_data[i] = val.value (); if (swap) swap_bytes<sizeof (val_type)> (&vt_data[i]); } } template <typename T> class ultimate_element_type { public: typedef T type; }; template <typename T> class ultimate_element_type<octave_int<T> > { public: typedef T type; }; template <typename T> static bool convert_data (const T *data, void *conv_data, octave_idx_type n_elts, oct_data_conv::data_type output_type, octave::mach_info::float_format flt_fmt) { bool retval = true; bool swap = false; if (octave::mach_info::words_big_endian ()) swap = (flt_fmt == octave::mach_info::flt_fmt_ieee_little_endian); else swap = (flt_fmt == octave::mach_info::flt_fmt_ieee_big_endian); bool do_float_conversion = flt_fmt != octave::mach_info::float_format (); typedef typename ultimate_element_type<T>::type ult_elt_type; switch (output_type) { case oct_data_conv::dt_char: convert_chars<ult_elt_type, char> (data, conv_data, n_elts); break; case oct_data_conv::dt_schar: convert_chars<ult_elt_type, signed char> (data, conv_data, n_elts); break; case oct_data_conv::dt_uchar: convert_chars<ult_elt_type, unsigned char> (data, conv_data, n_elts); break; case oct_data_conv::dt_int8: convert_ints<T, octave_int8> (data, conv_data, n_elts, swap); break; case oct_data_conv::dt_uint8: convert_ints<T, octave_uint8> (data, conv_data, n_elts, swap); break; case oct_data_conv::dt_int16: convert_ints<T, octave_int16> (data, conv_data, n_elts, swap); break; case oct_data_conv::dt_uint16: convert_ints<T, octave_uint16> (data, conv_data, n_elts, swap); break; case oct_data_conv::dt_int32: convert_ints<T, octave_int32> (data, conv_data, n_elts, swap); break; case oct_data_conv::dt_uint32: convert_ints<T, octave_uint32> (data, conv_data, n_elts, swap); break; case oct_data_conv::dt_int64: convert_ints<T, octave_int64> (data, conv_data, n_elts, swap); break; case oct_data_conv::dt_uint64: convert_ints<T, octave_uint64> (data, conv_data, n_elts, swap); break; case oct_data_conv::dt_single: { float *vt_data = static_cast<float *> (conv_data); for (octave_idx_type i = 0; i < n_elts; i++) { vt_data[i] = data[i]; if (do_float_conversion) do_float_format_conversion (&vt_data[i], 1, flt_fmt); } } break; case oct_data_conv::dt_double: { double *vt_data = static_cast<double *> (conv_data); for (octave_idx_type i = 0; i < n_elts; i++) { vt_data[i] = data[i]; if (do_float_conversion) do_double_format_conversion (&vt_data[i], 1, flt_fmt); } } break; default: ::error ("write: invalid type specification"); } return retval; } bool octave_stream::write_bytes (const void *data, size_t nbytes) { bool status = false; std::ostream *osp = output_stream (); if (osp) { std::ostream& os = *osp; if (os) { os.write (static_cast<const char *> (data), nbytes); if (os) status = true; } } return status; } bool octave_stream::skip_bytes (size_t skip) { bool status = false; std::ostream *osp = output_stream (); if (! osp) return false; std::ostream& os = *osp; // Seek to skip when inside bounds of existing file. // Otherwise, write NUL to skip. off_t orig_pos = tell (); seek (0, SEEK_END); off_t eof_pos = tell (); // Is it possible for this to fail to return us to the original position? seek (orig_pos, SEEK_SET); size_t remaining = eof_pos - orig_pos; if (remaining < skip) { seek (0, SEEK_END); // FIXME: probably should try to write larger blocks... unsigned char zero = 0; for (size_t j = 0; j < skip - remaining; j++) os.write (reinterpret_cast<const char *> (&zero), 1); } else seek (skip, SEEK_CUR); if (os) status = true; return status; } template <typename T> octave_idx_type octave_stream::write (const Array<T>& data, octave_idx_type block_size, oct_data_conv::data_type output_type, octave_idx_type skip, octave::mach_info::float_format flt_fmt) { bool swap = false; if (octave::mach_info::words_big_endian ()) swap = (flt_fmt == octave::mach_info::flt_fmt_ieee_little_endian); else swap = (flt_fmt == octave::mach_info::flt_fmt_ieee_big_endian); bool do_data_conversion = (swap || ! is_equivalent_type<T> (output_type) || flt_fmt != octave::mach_info::float_format ()); octave_idx_type nel = data.numel (); octave_idx_type chunk_size; if (skip != 0) chunk_size = block_size; else if (do_data_conversion) chunk_size = 1024 * 1024; else chunk_size = nel; octave_idx_type i = 0; const T *pdata = data.data (); while (i < nel) { if (skip != 0) { if (! skip_bytes (skip)) return -1; } octave_idx_type remaining_nel = nel - i; if (chunk_size > remaining_nel) chunk_size = remaining_nel; bool status = false; if (do_data_conversion) { size_t output_size = chunk_size * oct_data_conv::data_type_size (output_type); OCTAVE_LOCAL_BUFFER (unsigned char, conv_data, output_size); status = convert_data (&pdata[i], conv_data, chunk_size, output_type, flt_fmt); if (status) status = write_bytes (conv_data, output_size); } else status = write_bytes (pdata, sizeof (T) * chunk_size); if (! status) return -1; i += chunk_size; } return nel; } #define INSTANTIATE_WRITE(T) \ template \ octave_idx_type \ octave_stream::write (const Array<T>& data, octave_idx_type block_size, \ oct_data_conv::data_type output_type, \ octave_idx_type skip, \ octave::mach_info::float_format flt_fmt) INSTANTIATE_WRITE (octave_int8); INSTANTIATE_WRITE (octave_uint8); INSTANTIATE_WRITE (octave_int16); INSTANTIATE_WRITE (octave_uint16); INSTANTIATE_WRITE (octave_int32); INSTANTIATE_WRITE (octave_uint32); INSTANTIATE_WRITE (octave_int64); INSTANTIATE_WRITE (octave_uint64); INSTANTIATE_WRITE (int8_t); INSTANTIATE_WRITE (uint8_t); INSTANTIATE_WRITE (int16_t); INSTANTIATE_WRITE (uint16_t); INSTANTIATE_WRITE (int32_t); INSTANTIATE_WRITE (uint32_t); INSTANTIATE_WRITE (int64_t); INSTANTIATE_WRITE (uint64_t); INSTANTIATE_WRITE (bool); #if defined (OCTAVE_HAVE_OVERLOAD_CHAR_INT8_TYPES) INSTANTIATE_WRITE (char); #endif INSTANTIATE_WRITE (float); INSTANTIATE_WRITE (double); octave_value octave_stream::scanf (const std::string& fmt, const Array<double>& size, octave_idx_type& count, const std::string& who) { octave_value retval; if (stream_ok ()) retval = rep->scanf (fmt, size, count, who); return retval; } octave_value octave_stream::scanf (const octave_value& fmt, const Array<double>& size, octave_idx_type& count, const std::string& who) { octave_value retval = Matrix (); if (fmt.is_string ()) { std::string sfmt = fmt.string_value (); if (fmt.is_sq_string ()) sfmt = do_string_escapes (sfmt); retval = scanf (sfmt, size, count, who); } else { // Note: error is member fcn from octave_stream, not ::error. error (who + ": format must be a string"); } return retval; } octave_value_list octave_stream::oscanf (const std::string& fmt, const std::string& who) { octave_value_list retval; if (stream_ok ()) retval = rep->oscanf (fmt, who); return retval; } octave_value_list octave_stream::oscanf (const octave_value& fmt, const std::string& who) { octave_value_list retval; if (fmt.is_string ()) { std::string sfmt = fmt.string_value (); if (fmt.is_sq_string ()) sfmt = do_string_escapes (sfmt); retval = oscanf (sfmt, who); } else { // Note: error is member fcn from octave_stream, not ::error. error (who + ": format must be a string"); } return retval; } octave_value octave_stream::textscan (const std::string& fmt, octave_idx_type ntimes, const octave_value_list& options, const std::string& who, octave_idx_type& count) { return (stream_ok () ? rep->do_textscan (fmt, ntimes, options, who, count) : octave_value ()); } int octave_stream::printf (const std::string& fmt, const octave_value_list& args, const std::string& who) { int retval = -1; if (stream_ok ()) retval = rep->printf (fmt, args, who); return retval; } int octave_stream::printf (const octave_value& fmt, const octave_value_list& args, const std::string& who) { int retval = 0; if (fmt.is_string ()) { std::string sfmt = fmt.string_value (); if (fmt.is_sq_string ()) sfmt = do_string_escapes (sfmt); retval = printf (sfmt, args, who); } else { // Note: error is member fcn from octave_stream, not ::error. error (who + ": format must be a string"); } return retval; } int octave_stream::puts (const std::string& s, const std::string& who) { int retval = -1; if (stream_ok ()) retval = rep->puts (s, who); return retval; } // FIXME: maybe this should work for string arrays too. int octave_stream::puts (const octave_value& tc_s, const std::string& who) { int retval = -1; if (tc_s.is_string ()) { std::string s = tc_s.string_value (); retval = puts (s, who); } else { // Note: error is member fcn from octave_stream, not ::error. error (who + ": argument must be a string"); } return retval; } bool octave_stream::eof (void) const { int retval = -1; if (stream_ok ()) retval = rep->eof (); return retval; } std::string octave_stream::error (bool clear, int& err_num) { std::string retval = "invalid stream object"; if (stream_ok (false)) retval = rep->error (clear, err_num); return retval; } std::string octave_stream::name (void) const { std::string retval; if (stream_ok ()) retval = rep->name (); return retval; } int octave_stream::mode (void) const { int retval = 0; if (stream_ok ()) retval = rep->mode (); return retval; } octave::mach_info::float_format octave_stream::float_format (void) const { octave::mach_info::float_format retval = octave::mach_info::flt_fmt_unknown; if (stream_ok ()) retval = rep->float_format (); return retval; } std::string octave_stream::mode_as_string (int mode) { std::string retval = "???"; std::ios::openmode in_mode = static_cast<std::ios::openmode> (mode); if (in_mode == std::ios::in) retval = "r"; else if (in_mode == std::ios::out || in_mode == (std::ios::out | std::ios::trunc)) retval = "w"; else if (in_mode == (std::ios::out | std::ios::app)) retval = "a"; else if (in_mode == (std::ios::in | std::ios::out)) retval = "r+"; else if (in_mode == (std::ios::in | std::ios::out | std::ios::trunc)) retval = "w+"; else if (in_mode == (std::ios::in | std::ios::out | std::ios::ate)) retval = "a+"; else if (in_mode == (std::ios::in | std::ios::binary)) retval = "rb"; else if (in_mode == (std::ios::out | std::ios::binary) || in_mode == (std::ios::out | std::ios::trunc | std::ios::binary)) retval = "wb"; else if (in_mode == (std::ios::out | std::ios::app | std::ios::binary)) retval = "ab"; else if (in_mode == (std::ios::in | std::ios::out | std::ios::binary)) retval = "r+b"; else if (in_mode == (std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary)) retval = "w+b"; else if (in_mode == (std::ios::in | std::ios::out | std::ios::ate | std::ios::binary)) retval = "a+b"; return retval; } octave_stream_list *octave_stream_list::instance = 0; bool octave_stream_list::instance_ok (void) { bool retval = true; if (! instance) { instance = new octave_stream_list (); if (instance) singleton_cleanup_list::add (cleanup_instance); } if (! instance) ::error ("unable to create stream list object!"); return retval; } int octave_stream_list::insert (octave_stream& os) { return (instance_ok ()) ? instance->do_insert (os) : -1; } octave_stream octave_stream_list::lookup (int fid, const std::string& who) { return (instance_ok ()) ? instance->do_lookup (fid, who) : octave_stream (); } octave_stream octave_stream_list::lookup (const octave_value& fid, const std::string& who) { return (instance_ok ()) ? instance->do_lookup (fid, who) : octave_stream (); } int octave_stream_list::remove (int fid, const std::string& who) { return (instance_ok ()) ? instance->do_remove (fid, who) : -1; } int octave_stream_list::remove (const octave_value& fid, const std::string& who) { return (instance_ok ()) ? instance->do_remove (fid, who) : -1; } void octave_stream_list::clear (bool flush) { if (instance) instance->do_clear (flush); } string_vector octave_stream_list::get_info (int fid) { return (instance_ok ()) ? instance->do_get_info (fid) : string_vector (); } string_vector octave_stream_list::get_info (const octave_value& fid) { return (instance_ok ()) ? instance->do_get_info (fid) : string_vector (); } std::string octave_stream_list::list_open_files (void) { return (instance_ok ()) ? instance->do_list_open_files () : ""; } octave_value octave_stream_list::open_file_numbers (void) { return (instance_ok ()) ? instance->do_open_file_numbers () : octave_value (); } int octave_stream_list::get_file_number (const octave_value& fid) { return (instance_ok ()) ? instance->do_get_file_number (fid) : -1; } int octave_stream_list::do_insert (octave_stream& os) { // Insert item with key corresponding to file-descriptor. int stream_number = os.file_number (); if (stream_number == -1) return stream_number; // Should we test for // // (list.find (stream_number) != list.end () // && list[stream_number].is_open ()) // // and respond with "error ("internal error: ...")"? It should not // happen except for some bug or if the user has opened a stream with // an interpreted command, but closed it directly with a system call // in an oct-file; then the kernel knows the fd is free, but Octave // does not know. If it happens, it should not do harm here to simply // overwrite this entry, although the wrong entry might have done harm // before. if (list.size () >= list.max_size ()) ::error ("could not create file id"); list[stream_number] = os; return stream_number; } OCTAVE_NORETURN static void err_invalid_file_id (int fid, const std::string& who) { if (who.empty ()) ::error ("invalid stream number = %d", fid); else ::error ("%s: invalid stream number = %d", who.c_str (), fid); } octave_stream octave_stream_list::do_lookup (int fid, const std::string& who) const { octave_stream retval; if (fid < 0) err_invalid_file_id (fid, who); if (lookup_cache != list.end () && lookup_cache->first == fid) retval = lookup_cache->second; else { ostrl_map::const_iterator iter = list.find (fid); if (iter == list.end ()) err_invalid_file_id (fid, who); retval = iter->second; lookup_cache = iter; } return retval; } octave_stream octave_stream_list::do_lookup (const octave_value& fid, const std::string& who) const { int i = get_file_number (fid); return do_lookup (i, who); } int octave_stream_list::do_remove (int fid, const std::string& who) { // Can't remove stdin (std::cin), stdout (std::cout), or stderr (std::cerr). if (fid < 3) err_invalid_file_id (fid, who); ostrl_map::iterator iter = list.find (fid); if (iter == list.end ()) err_invalid_file_id (fid, who); octave_stream os = iter->second; list.erase (iter); lookup_cache = list.end (); // FIXME: is this check redundant? if (! os.is_valid ()) err_invalid_file_id (fid, who); os.close (); return 0; } int octave_stream_list::do_remove (const octave_value& fid, const std::string& who) { int retval = -1; if (fid.is_string () && fid.string_value () == "all") { do_clear (false); retval = 0; } else { int i = get_file_number (fid); retval = do_remove (i, who); } return retval; } void octave_stream_list::do_clear (bool flush) { if (flush) { // Flush stdout and stderr. list[1].flush (); list[2].flush (); } for (ostrl_map::iterator iter = list.begin (); iter != list.end (); ) { int fid = iter->first; if (fid < 3) // Don't delete stdin, stdout, stderr { iter++; continue; } octave_stream os = iter->second; std::string name = os.name (); std::transform (name.begin (), name.end (), name.begin (), tolower); // FIXME: This test for gnuplot is hardly foolproof. if (name.find ("gnuplot") != std::string::npos) { // Don't close down pipes to gnuplot iter++; continue; } // Normal file handle. Close and delete from list. if (os.is_valid ()) os.close (); list.erase (iter++); } lookup_cache = list.end (); } string_vector octave_stream_list::do_get_info (int fid) const { octave_stream os = do_lookup (fid); if (! os.is_valid ()) ::error ("invalid file id = %d", fid); string_vector retval (3); retval(0) = os.name (); retval(1) = octave_stream::mode_as_string (os.mode ()); retval(2) = octave::mach_info::float_format_as_string (os.float_format ()); return retval; } string_vector octave_stream_list::do_get_info (const octave_value& fid) const { int conv_err = 0; int int_fid = convert_to_valid_int (fid, conv_err); if (conv_err) ::error ("file id must be a file object or integer value"); return do_get_info (int_fid); } std::string octave_stream_list::do_list_open_files (void) const { std::ostringstream buf; buf << "\n" << " number mode arch name\n" << " ------ ---- ---- ----\n"; for (ostrl_map::const_iterator p = list.begin (); p != list.end (); p++) { octave_stream os = p->second; buf << " " << std::setiosflags (std::ios::right) << std::setw (4) << p->first << " " // reset necessary in addition to setiosflags since this is one stmt. << std::resetiosflags (std::ios::adjustfield) << std::setiosflags (std::ios::left) << std::setw (3) << octave_stream::mode_as_string (os.mode ()) << " " << std::setw (9) << octave::mach_info::float_format_as_string (os.float_format ()) << " " << os.name () << "\n"; } buf << "\n"; return buf.str (); } octave_value octave_stream_list::do_open_file_numbers (void) const { Matrix retval (1, list.size (), 0.0); int num_open = 0; for (ostrl_map::const_iterator p = list.begin (); p != list.end (); p++) { // Skip stdin, stdout, and stderr. if (p->first > 2 && p->second) retval(0,num_open++) = p->first; } retval.resize ((num_open > 0), num_open); return retval; } int octave_stream_list::do_get_file_number (const octave_value& fid) const { int retval = -1; if (fid.is_string ()) { std::string nm = fid.string_value (); for (ostrl_map::const_iterator p = list.begin (); p != list.end (); p++) { // stdin, stdout, and stderr are unnamed. if (p->first > 2) { octave_stream os = p->second; if (os && os.name () == nm) { retval = p->first; break; } } } } else { int conv_err = 0; int int_fid = convert_to_valid_int (fid, conv_err); if (conv_err) ::error ("file id must be a file object, std::string, or integer value"); retval = int_fid; } return retval; }