text: #include <fstream>
  #include <iostream>
  #include <sstream>
  #include <vector>
  #include <string>
  #include <map>
  #include <set>
  
  #include "readstat.h"
  #include "haven_types.h"
  #include "tagged_na.h"
  
  #include "cpp11/strings.hpp"
  #include "cpp11/doubles.hpp"
  #include "cpp11/integers.hpp"
  #include "cpp11/r_string.hpp"
  #include "cpp11/list.hpp"
  #include "cpp11/raws.hpp"
  #include "cpp11/sexp.hpp"
  
  #include "cpp11/protect.hpp"
  #include "cpp11/function.hpp"
  
  double haven_double_value_udm(readstat_value_t value, readstat_variable_t* var, bool user_na) {
    if (readstat_value_is_tagged_missing(value)) {
      return make_tagged_na(tolower(readstat_value_tag(value)));
    } else if (!user_na && readstat_value_is_defined_missing(value, var)) {
      return NA_REAL;
    } else if (readstat_value_is_system_missing(value)) {
      return NA_REAL;
    } else {
      return readstat_double_value(value);
    }
  }
  
  double haven_double_value(readstat_value_t value) {
    if (readstat_value_is_tagged_missing(value)) {
      return make_tagged_na(tolower(readstat_value_tag(value)));
    } else {
      return readstat_double_value(value);
    }
  }
  
  
  // LabelSet -------------------------------------------------------------------
  
  class LabelSet {
    std::vector<std::string> labels_;
    std::vector<std::string> values_s_;
    std::vector<int> values_i_;
    std::vector<double> values_d_;
  
  public:
    LabelSet() {}
  
    void add(const char* value, std::string label) {
      if (values_i_.size() > 0 || values_d_.size() > 0)
        cpp11::stop("Can't add string to integer/double labelset");
  
      values_s_.push_back(value);
      labels_.push_back(label);
    }
  
    void add(int value, std::string label) {
      if (values_d_.size() > 0 || values_s_.size() > 0)
        cpp11::stop("Can't add integer to string/double labelset");
  
      values_i_.push_back(value);
      labels_.push_back(label);
    }
  
    void add(double value, std::string label) {
      if (values_i_.size() > 0 || values_s_.size() > 0)
        cpp11::stop("Can't add double to integer/string labelset");
  
      values_d_.push_back(value);
      labels_.push_back(label);
    }
  
    size_t size() const {
      return labels_.size();
    }
  
    cpp11::sexp labels() const {
      cpp11::sexp out;
  
      if (values_i_.size() > 0) {
        int n = values_i_.size();
        cpp11::writable::integers values(n);
        cpp11::writable::strings labels(n);
  
        for (int i = 0; i < n; ++i) {
          values[i] = values_i_[i];
          labels[i] = labels_[i].c_str();
        }
  
        values.attr("names") = labels;
        out = values;
      } else if (values_d_.size() > 0) {
        int n = values_d_.size();
        cpp11::writable::doubles values(n);
        cpp11::writable::strings labels(n);
  
        for (int i = 0; i < n; ++i) {
          values[i] = values_d_[i];
          labels[i] = labels_[i].c_str();
        }
  
        values.attr("names") = labels;
        out = values;
      } else {
        int n = values_s_.size();
        cpp11::writable::strings values(n), labels(n);
  
        for (int i = 0; i < n; ++i) {
          values[i] = values_s_[i].c_str();
          labels[i] = labels_[i].c_str();
        }
  
        values.attr("names") = labels;
        out = values;
      }
  
      return out;
    }
  };
  
  // DfReader ------------------------------------------------------------------
  
  class DfReader {
    FileVendor vendor_;
  
    int nrows_, nrowsAlloc_;
    int ncols_;
    cpp11::writable::list output_;
    cpp11::writable::strings names_;
    bool user_na_;
  
    std::vector<std::string> val_labels_;
    std::map<std::string, LabelSet> label_sets_;
    std::vector<VarType> var_types_;
    std::vector<std::string> notes_;
  
    std::set<std::string> colsSkip_;
  
  public:
    DfReader(FileExt ext, bool user_na = false) :
      vendor_(extVendor(ext)),
      nrows_(0),
      ncols_(0),
      output_(static_cast<R_xlen_t>(0)),
      user_na_(user_na) { }
  
    void skipCols(const std::vector<std::string>& cols) {
      std::set<std::string> cols_set(cols.begin(), cols.end());
      colsSkip_ = cols_set;
    }
  
    void setInfo(int obs_count, int var_count) {
      if (obs_count < 0) {
        // If unknown, start with 1e5, and use doubling strategy
        nrowsAlloc_ = 1e5;
        nrows_ = 0;
      } else {
        nrowsAlloc_ = nrows_ = obs_count;
      }
  
      if (var_count < 1) {
        return; // sas7bcat has var_count = 0
      }
  
      ncols_ = var_count - colsSkip_.size();
  
      output_.resize(ncols_);
      names_.resize(ncols_);
      val_labels_.resize(ncols_);
      var_types_.resize(ncols_);
    }
  
    void setMetadata(const char *file_label) {
      if (file_label != NULL && strcmp(file_label, "") != 0) {
        output_.attr("label") = file_label;
      }
    }
  
    void setNote(int note_index, const char *note) {
      if (note != NULL && strcmp(note, "") != 0) {
        notes_.push_back(note);
      }
    }
  
    int createVariable(int index, readstat_variable_t *variable, const char *val_labels) {
  
      const char* name = readstat_variable_get_name(variable);
  
      if (colsSkip_.count(name) > 0) {
        return READSTAT_HANDLER_SKIP_VARIABLE;
      }
  
      int var_index = readstat_variable_get_index_after_skipping(variable);
  
      names_[var_index] = name;
  
      switch(readstat_variable_get_type(variable)) {
      case READSTAT_TYPE_STRING_REF:
      case READSTAT_TYPE_STRING:
        output_[var_index] = cpp11::writable::strings(nrowsAlloc_);
        break;
      case READSTAT_TYPE_INT8:
      case READSTAT_TYPE_INT16:
      case READSTAT_TYPE_INT32:
      case READSTAT_TYPE_FLOAT:
      case READSTAT_TYPE_DOUBLE:
        output_[var_index] = cpp11::writable::doubles(nrowsAlloc_);
        break;
      }
  
      cpp11::sexp col(output_[var_index]);
  
      const char* var_label = readstat_variable_get_label(variable);
      if (var_label != NULL && strcmp(var_label, "") != 0) {
        col.attr("label") = var_label;
      }
  
      if (val_labels != NULL)
        val_labels_[var_index] = val_labels;
  
      const char* var_format = readstat_variable_get_format(variable);
  
      VarType var_type = numType(vendor_, var_format);
      // Rcout << name << ": " << var_format << " [" << var_type << "]\n";
      var_types_[var_index] = var_type;
      switch(var_type) {
      case HAVEN_DATE:
        col.attr("class") = "Date";
        break;
      case HAVEN_TIME:
        col.attr("class") = {"hms", "difftime"};
        col.attr("units") = "secs";
        break;
      case HAVEN_DATETIME:
        col.attr("class") = {"POSIXct", "POSIXt"};
        col.attr("tzone") = "UTC";
        break;
      default:
        break;
      }
  
      // User defined missing values
      int n_ranges = readstat_variable_get_missing_ranges_count(variable);
      if (user_na_ && n_ranges > 0) {
        switch(readstat_variable_get_type(variable)) {
        case READSTAT_TYPE_STRING_REF:
        case READSTAT_TYPE_STRING:
        {
          cpp11::writable::strings na_values(n_ranges);
  
          for (int i = 0; i < n_ranges; ++i) {
            readstat_value_t value = readstat_variable_get_missing_range_lo(variable, i);
            const char* str_value = readstat_string_value(value);
            na_values[0] = str_value == NULL ? cpp11::r_string(NA_STRING) : cpp11::r_string(str_value);
          }
  
          col.attr("na_values") = na_values;
          col.attr("class") = {"haven_labelled_spss", "haven_labelled", "vctrs_vctr", "character"};
          break;
        }
        case READSTAT_TYPE_INT8:
        case READSTAT_TYPE_INT16:
        case READSTAT_TYPE_INT32:
        case READSTAT_TYPE_FLOAT:
        case READSTAT_TYPE_DOUBLE:
        {
          std::vector<double> na_values;
          cpp11::writable::doubles na_range(2);
          bool has_range = false;
  
          for (int i = 0; i < n_ranges; ++i) {
            readstat_value_t
              lo_value = readstat_variable_get_missing_range_lo(variable, i),
              hi_value = readstat_variable_get_missing_range_hi(variable, i);
  
            double
              lo = readstat_double_value(lo_value),
              hi = readstat_double_value(hi_value);
  
            if (lo == hi) {
              // Single value
              na_values.push_back(lo);
            } else {
              has_range = true;
              // Can only ever be one range
              na_range[0] = lo;
              na_range[1] = hi;
            }
          }
  
          if (na_values.size() > 0)
            col.attr("na_values") = na_values;
          if (has_range)
            col.attr("na_range") = na_range;
  
          col.attr("class") = {"haven_labelled_spss", "haven_labelled", "vctrs_vctr", "double"};
        }
        }
      }
  
  
      // Store original format as attribute
      if (var_format != NULL && strcmp(var_format, "") != 0) {
        col.attr(formatAttribute(vendor_)) = var_format;
      }
  
      // Store original display width as attribute if it differs from the default
      int display_width = readstat_variable_get_display_width(variable);
      if (vendor_ == HAVEN_SPSS && display_width != 8) {
        col.attr("display_width") = Rf_ScalarInteger(display_width);
      }
  
      return READSTAT_HANDLER_OK;
    }
  
    void setValue(int obs_index, readstat_variable_t *variable, readstat_value_t value) {
      int var_index = readstat_variable_get_index_after_skipping(variable);
  
      VarType var_type = var_types_[var_index];
  
      if (obs_index >= nrowsAlloc_)
        resizeCols(nrowsAlloc_ * 2);
      if (obs_index >= nrows_)
        nrows_ = obs_index + 1;
  
      switch(value.type) {
      case READSTAT_TYPE_STRING_REF:
      case READSTAT_TYPE_STRING:
      {
        cpp11::writable::strings col(output_[var_index]);
        const char* str_value = readstat_string_value(value);
        col[obs_index] = str_value == NULL ? cpp11::r_string(NA_STRING) : cpp11::r_string(str_value);
        break;
      }
      case READSTAT_TYPE_INT8:
      case READSTAT_TYPE_INT16:
      case READSTAT_TYPE_INT32:
      case READSTAT_TYPE_FLOAT:
      case READSTAT_TYPE_DOUBLE:
      {
        cpp11::writable::doubles col(output_[var_index]);
        double val = haven_double_value_udm(value, variable, user_na_);
        col[obs_index] = adjustDatetimeToR(vendor_, var_type, val);
        break;
      }
      }
    }
  
    void setValueLabels(const char *val_labels, readstat_value_t value,
                       const char *label) {
      LabelSet& label_set = label_sets_[val_labels];
      std::string label_s(label);
  
      switch(value.type) {
      case READSTAT_TYPE_STRING:
        // Encoded to utf-8 on output
        label_set.add(readstat_string_value(value), label_s);
        break;
      case READSTAT_TYPE_INT8:
      case READSTAT_TYPE_INT16:
      case READSTAT_TYPE_INT32:
      case READSTAT_TYPE_DOUBLE:
        label_set.add(haven_double_value(value), label_s);
        break;
      default:
        Rf_warning("Unsupported label type: %s", value.type);
      }
    }
  
    bool hasLabel(int var_index) const {
      std::string label = val_labels_[var_index];
      if (label == "")
        return false;
  
      return label_sets_.count(label) > 0;
    }
  
    void resizeCols(int n) {
      // Rcout << "resizing to " << n << "\n";
      nrowsAlloc_ = n;
  
      for (int i = 0; i < ncols_; ++i) {
        cpp11::sexp copy(Rf_lengthgets(output_[i], n));
        Rf_copyMostAttrib(output_[i], copy);
        output_[i] = copy;
      }
    }
  
    void limitRows(long n) {
      if (nrows_ > n) {
        nrows_ = n;
      }
    }
  
    cpp11::list output(const std::string& name_repair) {
      if (nrows_ != nrowsAlloc_)
        resizeCols(nrows_);
  
      for (int i = 0; i < output_.size(); ++i) {
        cpp11::sexp col(output_[i]);
  
        if (hasLabel(i)) {
          if (Rf_getAttrib(col, R_ClassSymbol) == R_NilValue) {
            col.attr("class") = {"haven_labelled", "vctrs_vctr", Rf_type2char(TYPEOF(col))};
          }
          col.attr("labels") = label_sets_[val_labels_[i]].labels();
        }
      }
  
      int nNotes = notes_.size();
      if (nNotes > 0) {
        cpp11::writable::strings notes(nNotes);
  
        for (int i = 0; i < nNotes; ++i) {
          notes[i] = notes_[i].c_str();
        }
  
        output_.attr("notes") = notes_;
      }
  
      output_.attr("names") = names_;
  
      static cpp11::function as_tibble = cpp11::package("tibble")["as_tibble"];
      using namespace cpp11::literals;
      return SEXP(as_tibble(output_, ".rows"_nm = nrows_, ".name_repair"_nm = name_repair));
    }
  };
  
  int dfreader_metadata(readstat_metadata_t *metadata, void *ctx) {
    ((DfReader*) ctx)->setInfo(
        readstat_get_row_count(metadata),
        readstat_get_var_count(metadata)
    );
    ((DfReader*) ctx)->setMetadata(readstat_get_file_label(metadata));
    return 0;
  }
  
  int dfreader_note(int note_index, const char *note, void *ctx) {
    ((DfReader*) ctx)->setNote(note_index, note);
    return 0;
  }
  
  int dfreader_variable(int index, readstat_variable_t *variable,
                        const char *val_labels, void *ctx) {
    return ((DfReader*) ctx)->createVariable(index, variable, val_labels);
  }
  int dfreader_value(int obs_index, readstat_variable_t *variable,
                     readstat_value_t value, void *ctx) {
    // Check for user interrupts every 10,000 rows or cols
    if ((obs_index + 1) % 10000 == 0 || (variable->index + 1) % 10000 == 0)
      cpp11::check_user_interrupt();
  
    ((DfReader*) ctx)->setValue(obs_index, variable, value);
    return 0;
  }
  int dfreader_value_label(const char *val_labels, readstat_value_t value,
                           const char *label, void *ctx) {
    ((DfReader*) ctx)->setValueLabels(val_labels, value, label);
    return 0;
  }
  
  void print_error(const char* error_message, void* ctx) {
    Rprintf("%s\n", error_message);
  }
  
  
  // IO handling -----------------------------------------------------------
  
  class DfReaderInput {
  public:
    virtual ~DfReaderInput() {};
  
    virtual int open(void* io_ctx) = 0;
    virtual int close(void* io_ctx) = 0;
    virtual readstat_off_t seek(readstat_off_t offset, readstat_io_flags_t whence, void *io_ctx) = 0;
    virtual ssize_t read(void *buf, size_t nbyte, void *io_ctx) = 0;
    virtual std::string source() const = 0; // human readable description of input source
    std::string encoding;
  };
  
  template <typename Stream>
  class DfReaderInputStream : public DfReaderInput {
  protected:
    Stream file_;
  
  public:
    readstat_off_t seek(readstat_off_t offset, readstat_io_flags_t whence, void *io_ctx) {
      std::ios_base::seekdir dir;
      switch(whence) {
      case READSTAT_SEEK_SET: dir = file_.beg; break;
      case READSTAT_SEEK_CUR: dir = file_.cur; break;
      case READSTAT_SEEK_END:
      default:                dir = file_.end; break;
      }
  
      file_.seekg(offset, dir);
      return file_.tellg(); // returns -1 if failed
    }
  
    ssize_t read(void *buf, size_t nbyte, void *io_ctx) {
      file_.read((char*) buf, nbyte);
      return (file_.good() || file_.eof()) ? file_.gcount() : -1;
    }
  };
  
  class DfReaderInputFile : public DfReaderInputStream<std::ifstream> {
    std::string filename_;
  
  public:
    DfReaderInputFile(cpp11::list spec, std::string encoding = "") {
      cpp11::strings path(spec[0]);
      filename_ = std::string(Rf_translateChar(path[0]));
      this->encoding = encoding;
    }
  
    std::string source() const {
      return filename_;
    }
  
    int open(void* io_ctx) {
      file_.open(filename_.c_str(), std::ifstream::binary);
      return file_.is_open() ? 0 : -1;
    }
  
    int close(void* io_ctx) {
      file_.close();
      return file_.is_open() ? -1 : 0;
    }
  };
  
  class DfReaderInputRaw : public DfReaderInputStream<std::istringstream> {
  public:
    DfReaderInputRaw(cpp11::list spec, std::string encoding = "") {
      cpp11::raws raw_data(spec[0]);
      std::string string_data((char*) RAW(raw_data), Rf_length(raw_data));
      file_.str(string_data);
      this->encoding = encoding;
    }
  
    std::string source() const {
      return "file";
    }
  
    int open(void* io_ctx) {
      return 0;
    }
  
    int close(void* io_ctx) {
      return 0;
    }
  };
  
  
  int dfreader_open(const char* path, void *io_ctx) {
    return ((DfReaderInput*) io_ctx)->open(io_ctx);
  }
  int dfreader_close(void *io_ctx) {
    return ((DfReaderInput*) io_ctx)->close(io_ctx);
  }
  readstat_off_t dfreader_seek(readstat_off_t offset, readstat_io_flags_t whence, void* io_ctx) {
    return ((DfReaderInput*) io_ctx)->seek(offset, whence, io_ctx);
  }
  ssize_t dfreader_read(void* buf, size_t nbyte, void* io_ctx) {
    return ((DfReaderInput*) io_ctx)->read(buf, nbyte, io_ctx);
  }
  readstat_error_t dfreader_update(long file_size, readstat_progress_handler progress_handler, void *user_ctx, void *io_ctx) {
    return READSTAT_OK;
  }
  
  // Parser wrappers -------------------------------------------------------------
  
  readstat_parser_t* haven_init_parser() {
    readstat_parser_t* parser = readstat_parser_init();
    readstat_set_metadata_handler(parser, dfreader_metadata);
    readstat_set_note_handler(parser, dfreader_note);
    readstat_set_variable_handler(parser, dfreader_variable);
    readstat_set_value_handler(parser, dfreader_value);
    readstat_set_value_label_handler(parser, dfreader_value_label);
    readstat_set_error_handler(parser, print_error);
    return parser;
  }
  
  void haven_init_io(readstat_parser_t* parser, DfReaderInput& builder_input) {
    readstat_set_open_handler(parser, dfreader_open);
    readstat_set_close_handler(parser, dfreader_close);
    readstat_set_seek_handler(parser, dfreader_seek);
    readstat_set_read_handler(parser, dfreader_read);
    readstat_set_update_handler(parser, dfreader_update);
    readstat_set_io_ctx(parser, (void*) &builder_input);
    if (builder_input.encoding != "") {
      readstat_set_file_character_encoding(parser, builder_input.encoding.c_str());
    }
  }
  
  void haven_set_row_limit(readstat_parser_t* parser, long n) {
    // readstat uses 0 to specify "all rows" but what we want is "minimal rows"
    readstat_set_row_limit(parser, n == 0 ? 1 : n);
  }
  
  template<FileExt ext>
  void haven_parse(readstat_parser_t* parser, DfReaderInput& builder_input, DfReader* builder) {
    haven_init_io(parser, builder_input);
  
    readstat_error_t result;
    switch(ext) {
    // the path for readstat_parse_* is baked into the io context (DfReaderInput)
    case HAVEN_SAS7BDAT: result = readstat_parse_sas7bdat(parser, "", builder); break;
    case HAVEN_SAS7BCAT: result = readstat_parse_sas7bcat(parser, "", builder); break;
    case HAVEN_XPT:      result = readstat_parse_xport(parser, "", builder); break;
    case HAVEN_DTA:      result = readstat_parse_dta(parser, "", builder); break;
    case HAVEN_SAV:      result = readstat_parse_sav(parser, "", builder); break;
    case HAVEN_POR:      result = readstat_parse_por(parser, "", builder); break;
    default:             result = READSTAT_ERROR_PARSE; break;
    }
  
    if (result != READSTAT_OK) {
      std::string source = builder_input.source();
      readstat_parser_free(parser);
      std::string msg(readstat_error_message(result));
      cpp11::stop("Failed to parse %s: %s.", source.c_str(), msg.c_str());
    }
  }
  
  template<FileExt ext, typename InputClass>
  cpp11::list df_parse(cpp11::list spec, const std::vector<std::string>& cols_skip,
                const long& n_max = -1, const long& rows_skip = 0,
                const std::string& encoding = "",
                const bool& user_na = false,
                const std::string& name_repair = "check_unique",
                cpp11::list catalog_spec = cpp11::writable::list(R_xlen_t(0)),
                const std::string& catalog_encoding = ""
                ) {
    DfReader builder(ext, user_na);
    builder.skipCols(cols_skip);
  
    readstat_parser_t* parser = haven_init_parser();
    haven_set_row_limit(parser, n_max);
    readstat_set_row_offset(parser, rows_skip);
  
    if (ext == HAVEN_SAS7BDAT && catalog_spec.size() != 0) {
      InputClass cat_builder_input(catalog_spec, catalog_encoding);
      haven_parse<HAVEN_SAS7BCAT>(parser, cat_builder_input, &builder);
    }
  
    InputClass builder_input(spec, encoding);
    haven_parse<ext>(parser, builder_input, &builder);
    readstat_parser_free(parser);
  
    if (n_max >= 0) {
      builder.limitRows(n_max); // must enforce n_max = 0
    }
  
    return builder.output(name_repair);
  }
  
  // # nocov start
  
  [[cpp11::register]]
  cpp11::list df_parse_sas_file(cpp11::list spec_b7dat, cpp11::list spec_b7cat,
                         std::string encoding, std::string catalog_encoding,
                         std::vector<std::string> cols_skip, long n_max, long rows_skip,
                         std::string name_repair) {
    return df_parse<HAVEN_SAS7BDAT, DfReaderInputFile>(spec_b7dat, cols_skip, n_max, rows_skip, encoding, false, name_repair, spec_b7cat, catalog_encoding);
  }
  [[cpp11::register]]
  cpp11::list df_parse_sas_raw(cpp11::list spec_b7dat, cpp11::list spec_b7cat,
                        std::string encoding, std::string catalog_encoding,
                        std::vector<std::string> cols_skip, long n_max, long rows_skip,
                        std::string name_repair) {
    return df_parse<HAVEN_SAS7BDAT, DfReaderInputRaw>(spec_b7dat, cols_skip, n_max, rows_skip, encoding, false, name_repair, spec_b7cat, catalog_encoding);
  }
  
  [[cpp11::register]]
  cpp11::list df_parse_xpt_file(cpp11::list spec, std::vector<std::string> cols_skip, long n_max, long rows_skip, std::string name_repair) {
    return df_parse<HAVEN_XPT, DfReaderInputFile>(spec, cols_skip, n_max, rows_skip, "", false, name_repair);
  }
  [[cpp11::register]]
  cpp11::list df_parse_xpt_raw(cpp11::list spec, std::vector<std::string> cols_skip, long n_max, long rows_skip, std::string name_repair) {
    return df_parse<HAVEN_XPT, DfReaderInputRaw>(spec, cols_skip, n_max, rows_skip, "", false, name_repair);
  }
  
  [[cpp11::register]]
  cpp11::list df_parse_dta_file(cpp11::list spec, std::string encoding, std::vector<std::string> cols_skip, long n_max, long rows_skip, std::string name_repair) {
    return df_parse<HAVEN_DTA, DfReaderInputFile>(spec, cols_skip, n_max, rows_skip, encoding, false, name_repair);
  }
  [[cpp11::register]]
  cpp11::list df_parse_dta_raw(cpp11::list spec, std::string encoding, std::vector<std::string> cols_skip, long n_max, long rows_skip, std::string name_repair) {
    return df_parse<HAVEN_DTA, DfReaderInputRaw>(spec, cols_skip, n_max, rows_skip, encoding, false, name_repair);
  }
  
  [[cpp11::register]]
  cpp11::list df_parse_sav_file(cpp11::list spec, std::string encoding, bool user_na, std::vector<std::string> cols_skip, long n_max, long rows_skip, std::string name_repair) {
    return df_parse<HAVEN_SAV, DfReaderInputFile>(spec, cols_skip, n_max, rows_skip, encoding, user_na, name_repair);
  }
  [[cpp11::register]]
  cpp11::list df_parse_sav_raw(cpp11::list spec, std::string encoding, bool user_na, std::vector<std::string> cols_skip, long n_max, long rows_skip, std::string name_repair) {
    return df_parse<HAVEN_SAV, DfReaderInputRaw>(spec, cols_skip, n_max, rows_skip, encoding, user_na, name_repair);
  }
  
  [[cpp11::register]]
  cpp11::list df_parse_por_file(cpp11::list spec, std::string encoding, bool user_na, std::vector<std::string> cols_skip, long n_max, long rows_skip, std::string name_repair) {
    return df_parse<HAVEN_POR, DfReaderInputFile>(spec, cols_skip, n_max, rows_skip, encoding, user_na, name_repair);
  }
  [[cpp11::register]]
  cpp11::list df_parse_por_raw(cpp11::list spec, std::string encoding, bool user_na, std::vector<std::string> cols_skip, long n_max, long rows_skip, std::string name_repair) {
    return df_parse<HAVEN_POR, DfReaderInputRaw>(spec, cols_skip, n_max, rows_skip, encoding, user_na, name_repair);
  }
  
  // # nocov end
  