Exemplo n.º 1
0
void OutputTask::Run()
{
	set_thread_name("OutputTask");

	MatchList ml;
	bool first_matchlist_printed = false;
	std::stringstream sstrm;

	while(m_input_queue.wait_pull(std::move(ml)) != queue_op_status::closed)
	{
		if(first_matchlist_printed && m_output_is_tty)
		{
			// Print a blank line between the match lists (i.e. the groups of matches in one file).
			std::cout << "\n";
		}
		ml.Print(sstrm, m_output_is_tty, m_enable_color, m_print_column);
		std::cout << sstrm.str();
		std::cout.flush();
		sstrm.str(std::string());
		sstrm.clear();
		first_matchlist_printed = true;

		// Count up the total number of matches.
		m_total_matched_lines += ml.GetNumberOfMatchedLines();
	}
}
Exemplo n.º 2
0
MatchList::const_iterator filterOutBestMatch(const MatchList &input) {
	MatchList::const_iterator result = input.begin();

	if (input.size() > 1)
		warning("Multiple entries found for id %d/%s", (*result)->first, getIdString((*result)->first));

	for (MatchList::const_iterator i = input.begin(); i != input.end(); ++i) {
		// Reduce all entries to one single entry.
		//
		// We use the following rules for this (in this order):
		// - Prefer the entry with the higest size
		// - Prefer the entry, which starts at the smallest offest
		//
		// TODO: These rules might not be safe for all games, but hopefully
		// they will work fine. If there are any problems it should be rather
		// easy to identify them, since we print out a warning for multiple
		// entries found.
		if ((*result)->second.desc.hint.size <= (*i)->second.desc.hint.size) {
			if ((*result)->second.offset >= (*i)->second.offset)
				result = i;
		}
	}

	return result;
}
Exemplo n.º 3
0
upSimpleReport MatchResultList::regenerateReport()
{
  // collect the numbers of all match groups in this round
  MatchMngr mm{db};
  MatchGroupList mgl = mm.getMatchGroupsForCat(cat, round);
  if (mgl.size() > 1)
  {
    std::sort(mgl.begin(), mgl.end(), [](MatchGroup& mg1, MatchGroup& mg2){
      if (mg1.getGroupNumber() < mg2.getGroupNumber()) return true;
      return false;
    });
  }

  // prepare a subheader if we are in KO-rounds
  QString subHeader = QString();
  if ((mgl.size() == 1) && (mgl.at(0).getGroupNumber() > 0))
  {
    subHeader = GuiHelpers::groupNumToLongString(mgl.at(0).getGroupNumber());
  }

  upSimpleReport result = createEmptyReport_Portrait();
  QString repName = cat.getName() + tr(" -- Results of Round ") + QString::number(round);
  setHeaderAndHeadline(result.get(), repName, subHeader);

  // print a warning if the round is incomplete
  CatRoundStatus crs = cat.getRoundStatus();
  if (round > crs.getFinishedRoundsCount())
  {
    result->writeLine(tr("Note: the round is not finished yet; results are incomplete."), "", 1.0);
  }

  // print the results of each match group
  for (MatchGroup mg : mgl)
  {
    int grpNum = mg.getGroupNumber();

    // print a header if we are in round-robin rounds
    if (grpNum > 0)
    {
      printIntermediateHeader(result, GuiHelpers::groupNumToLongString(grpNum));
    }

    // print each finished match
    MatchList ml = mg.getMatches();
    std::sort(ml.begin(), ml.end(), [](Match& m1, Match& m2){
      return (m1.getMatchNumber() < m2.getMatchNumber());
    });
    printMatchList(result, ml, PlayerPairList(), GuiHelpers::groupNumToLongString(grpNum) + tr(" (cont.)"), true, false);
    result->skip(3.0);
  }

  // set header and footer
  setHeaderAndFooter(result, repName);

  return result;
}
Exemplo n.º 4
0
void HammingMatchBatch::getResult(MatchList &good) const{
	good.clear();
	cv::DMatch _mtch;
	for ( int _i = 0; _i < this->result.rows; ++_i){
		_mtch.queryIdx = this->result.at<int>(_i, 0);
		_mtch.trainIdx = this->result.at<int>(_i, 1);
		_mtch.distance = this->result.at<int>(_i, 2);
		if ( _mtch.queryIdx != -1 ) good.push_back(_mtch);
	}
}
Exemplo n.º 5
0
MatchList filterLanguageMatches(const int lang, const MatchList &input) {
	std::list<ExtractMap::const_iterator> result;

	for (MatchList::const_iterator i = input.begin(); i != input.end(); ++i) {
		if ((*i)->second.desc.lang == lang)
			result.push_back(*i);
	}

	return result;
}
upSimpleReport MatchResultList_ByGroup::regenerateReport()
{
  // collect the match groups with the requested match group number and
  // search in all rounds
  MatchMngr mm{db};
  MatchGroupList mgl = mm.getMatchGroupsForCat(cat);
  MatchGroupList filteredList;
  for (MatchGroup mg: mgl)
  {
    if (mg.getGroupNumber() == grpNum) filteredList.push_back(mg);
  }

  // sort match groups by round number
  if (filteredList.size() > 1)
  {
    std::sort(filteredList.begin(), filteredList.end(), [](MatchGroup& mg1, MatchGroup& mg2){
      if (mg1.getRound() < mg2.getRound()) return true;
      return false;
    });
  }

  upSimpleReport result = createEmptyReport_Portrait();
  QString repName = cat.getName() + tr(" -- Results of Group ") + QString::number(grpNum);
  setHeaderAndHeadline(result.get(), repName);

  for (MatchGroup mg : filteredList)
  {
    int round = mg.getRound();
    printIntermediateHeader(result, tr("Round ") + QString::number(round));

    MatchList maList = mg.getMatches();
    std::sort(maList.begin(), maList.end(), [](Match& ma1, Match& ma2)
    {
      return ma1.getMatchNumber() < ma2.getMatchNumber();
    });

    printMatchList(result, maList, PlayerPairList(), tr("Results of round ") + QString::number(round) + tr(" (cont.)"), true, false);

    if (mg.getState() != STAT_MG_FINISHED)
    {
      result->skip(1.0);
      result->writeLine(tr("Note: this round is not finished yet; results for this group can be incomplete."));
    }

    result->skip(3.0);
  }

  // set header and footer
  setHeaderAndFooter(result, repName);

  return result;
}
Exemplo n.º 7
0
 // Check each predicate in the MatchList for common sub-expressions
 static void cse_matchlist(MatchList *matchList) {
     for( MatchList *mList = matchList; mList != NULL; mList = mList->get_next() ) {
         Predicate* predicate = mList->get_pred_obj();
         char*      pred      = mList->get_pred();
         if( pred != NULL ) {
             for(int index = 0; index < count; ++index ) {
                 const char *shared_pred      = dfa_shared_preds::pred(index);
                 const char *shared_pred_var  = dfa_shared_preds::var(index);
                 bool result = dfa_shared_preds::cse_predicate(predicate, shared_pred, shared_pred_var);
                 if( result ) dfa_shared_preds::set_found(index, true);
             }
         }
     }
 }
Exemplo n.º 8
0
void fast_hammingnorm_matchserial(const cv::Mat &des1, const cv::Mat &des2, ListIdxCorrespondance &_list, const float ratio){
	MatchList good;
	good.clear();
	bool flipcorrespondence = false;
	if ( des1.rows <= des2.rows ){
		_hammingmatchserial(des1, des2, good, ratio);
	} else {
		flipcorrespondence = true;
		_hammingmatchserial(des2, des1, good, ratio);
	}	
	correspondance_list(good, _list, flipcorrespondence);
	
	
}
Exemplo n.º 9
0
void SearchSet::addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority) {
	FSList subDirs;
	if (!directory.getChildren(subDirs))
		return;

	String nextPattern, pattern;
	String::const_iterator sep = Common::find(origPattern.begin(), origPattern.end(), '/');
	if (sep != origPattern.end()) {
		pattern = String(origPattern.begin(), sep);

		++sep;
		if (sep != origPattern.end())
			nextPattern = String(sep, origPattern.end());
	}
	else {
		pattern = origPattern;
	}

	// TODO: The code we have for displaying all matches, which vary only in case, might
	// be a bit overhead, but as long as we want to display all useful information to the
	// user we will need to keep track of all directory names added so far. We might
	// want to reconsider this though.
	typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> MatchList;
	MatchList multipleMatches;
	MatchList::iterator matchIter;

	for (FSList::const_iterator i = subDirs.begin(); i != subDirs.end(); ++i) {
		String name = i->getName();

		if (Common::matchString(name.c_str(), pattern.c_str(), ignoreCase)) {
			matchIter = multipleMatches.find(name);
			if (matchIter == multipleMatches.end()) {
				multipleMatches[name] = true;
			} else {
				if (matchIter->_value) {
					warning("Clash in case for match of pattern \"%s\" found in directory \"%s\": \"%s\"", pattern.c_str(), directory.getPath().c_str(), matchIter->_key.c_str());
					matchIter->_value = false;
				}

				warning("Clash in case for match of pattern \"%s\" found in directory \"%s\": \"%s\"", pattern.c_str(), directory.getPath().c_str(), name.c_str());
			}

			if (nextPattern.empty())
				addDirectory(name, *i, priority);
			else
				addSubDirectoriesMatching(*i, nextPattern, ignoreCase, priority);
		}
	}
}
Exemplo n.º 10
0
/** Calculates scores for each player (who played at least one match)
 *  and returns list of results
 */
PlayerResultsList Group::playersResults() const 
{
  PlayerResultsList list;
  for ( int i = 0; i < _players.count(); i ++ ) {
    Player p = _players.at( i );
    MatchList ml = playedMatchList( p );

    if ( !ml.isEmpty() ) {
      PlayerResults res( p, ml );
      list << res;
    }
  }

  return list;
}
Exemplo n.º 11
0
void _hammingmatch_lowetest_slow(const cv::Mat &_des1, const cv::Mat &_des2, MatchList &_good, const float ratio){
	// compute and copmare norms row by row
	int _n1 = _des1.rows;
	int _n2 = _des2.rows;
	int _idx, _mindist1, _mindist2;
	cv::DMatch _mtch;
	for ( int _i = 0; _i < _n1; ++_i ){
		_idx=-1;	_mindist1 = 10000;	_mindist2 = 1000000;	// some arbirtrary large value for initializing
		// compute best idx for row _i of _des1
		for ( int _j = 0; _j < _n2; ++_j ){
			// calculate hamming distance
			int _val = cv::normHamming(_des1.row(_i).data, _des2.row(_j).data, 4);
			if ( _val > 8) {continue;}	// skip to make it fast
			_val = cv::normHamming(_des1.row(_i).data, _des2.row(_j).data, _des2.cols);
			if ( _val < _mindist2 ){
			if ( _val < _mindist1 ){
				_mindist2 = _mindist1;
				_mindist1 = _val;
				_idx = _j;
			} else _mindist2 = _val;
			}
		}
		// ratio test
		if ( (_idx != -1) && (_mindist1 < (_mindist2 * ratio ))){
			// all is okay
			printf("distance %d: %d\n", _i, _mindist1);
			_mtch.distance = _mindist1;
			_mtch.queryIdx = _i;		// the first
			_mtch.trainIdx = _idx;		// the second
			_good.push_back(_mtch);
		}
	}
}
Exemplo n.º 12
0
Homography meanHomography(const Array<Feat>& feats1,const Array<Feat>& feats2,const MatchList& matches) {
    int nb=int(matches.size());
    Matrix<double> A(2*nb,8);
    Vector<double> B(2*nb);
    A.fill(0);
    int k=0;
    // Completer: remplir A et B pour que H verifie AH=B
    for (MatchList::const_iterator it=matches.begin(); it!=matches.end(); k++,it++) {
        Vec2 m1=feats1[it->first].pos;
        Vec2 m2=feats2[it->second].pos;
        // ...
    }
    Matrix<double> C=pseudoInverse(A);	// Moindres carr�s
    if(norm(C)==0)	// non invertible
        return Homography(0.);
    Vector<double> H=C*B;
    return Homography(H.data());	// Painlesss Vector -> FVector conversion
}
Exemplo n.º 13
0
void fast_hammingnorm_match_neighborhood(const cv::Mat& des1, const cv::Mat& des2, const cv::Mat& neighborhood,
										 ListIdxCorrespondance& _list, const float ratio, const int parscore)
{
	cv::setNumThreads(3);
	MatchList good;
	good.clear();
	bool flipcorrespondence = false;
	if ( des1.rows <= des2.rows ){
		HammingMatchBatch _hmtbb(des1, des2,neighborhood, ratio, parscore);
		cv::parallel_for_(cv::Range(0, des1.rows), _hmtbb, 3);
		_hmtbb.getResult(good);
	} else {
		flipcorrespondence = true;
		cv::Mat _neight=neighborhood.t();
		HammingMatchBatch _hmtbb(des2, des1, _neight, ratio, parscore);
		cv::parallel_for_(cv::Range(0, des2.rows), _hmtbb, 3);
		_hmtbb.getResult(good);
	}	
	correspondance_list(good, _list, flipcorrespondence);
}
Exemplo n.º 14
0
void fast_hammingnorm_match(const cv::Mat &des1, const cv::Mat&des2, ListIdxCorrespondance &_list,
							const float ratio, const int parscore){
	// Fast Hamming Norm based test
	cv::setNumThreads(3);
	cv::Mat empty;
	MatchList good;
	good.clear();
	bool flipcorrespondence = false;
	if ( des1.rows <= des2.rows ){
		HammingMatchBatch _hmtbb(des1, des2, empty, ratio, parscore);		
		cv::parallel_for_(cv::Range(0, des1.rows), _hmtbb, 3);
		_hmtbb.getResult(good);
	} else {
		flipcorrespondence = true;
		HammingMatchBatch _hmtbb(des2, des1, empty, ratio, parscore);
		cv::parallel_for_(cv::Range(0, des2.rows), _hmtbb, 3);
		_hmtbb.getResult(good);
	}	
	correspondance_list(good, _list, flipcorrespondence);
}
Exemplo n.º 15
0
void ArchDesc::gen_dfa_state_body(FILE* fp, Dict &minimize, ProductionState &status, Dict &operands_chained_from, int i) {
    // Start the body of each Op_XXX sub-dfa with a clean state.
    status.initialize();

    // Walk the list, compacting it
    MatchList* mList = _mlistab[i];
    do {
        // Hash each entry using inputs as key and pointer as data.
        // If there is already an entry, keep the one with lower cost, and
        // remove the other one from the list.
        prune_matchlist(minimize, *mList);
        // Iterate
        mList = mList->get_next();
    } while(mList != NULL);

    // Hoist previously specified common sub-expressions out of predicates
    dfa_shared_preds::reset_found();
    dfa_shared_preds::cse_matchlist(_mlistab[i]);
    dfa_shared_preds::generate_cse(fp);

    mList = _mlistab[i];

    // Walk the list again, generating code
    do {
        // Each match can generate its own chains
        operands_chained_from.Clear();
        gen_match(fp, *mList, status, operands_chained_from);
        mList = mList->get_next();
    } while(mList != NULL);
    // Fill in any chain rules which add instructions
    // These can generate their own chains as well.
    operands_chained_from.Clear();  //
    if( debug_output1 ) {
        fprintf(fp, "// top level chain rules for: %s \n", (char *)NodeClassNames[i]);    // %%%%% Explanation
    }
    const Expr *zeroCost = new Expr("0");
    chain_rule(fp, "   ", (char *)NodeClassNames[i], zeroCost, "Invalid",
               operands_chained_from, status);
}
Exemplo n.º 16
0
MatchList filterPlatformMatches(const Game *g, std::pair<ExtractMap::const_iterator, ExtractMap::const_iterator> range) {
	bool hasPlatformMatch = false;
	for (ExtractMap::const_iterator i = range.first; i != range.second; ++i) {
		if (i->second.desc.platform == g->platform) {
			hasPlatformMatch = true;
			break;
		}
	}

	MatchList result;
	if (hasPlatformMatch) {
		for (ExtractMap::const_iterator i = range.first; i != range.second; ++i) {
			if (i->second.desc.platform == g->platform)
				result.push_back(i);
		}
	} else {
		for (ExtractMap::const_iterator i = range.first; i != range.second; ++i)
			result.push_back(i);
	}

	return result;
}
Exemplo n.º 17
0
//---------------------------gen_match-----------------------------------------
void ArchDesc::gen_match(FILE *fp, MatchList &mList, ProductionState &status, Dict &operands_chained_from) {
    const char *spaces4 = "    ";
    const char *spaces6 = "      ";

    fprintf(fp, "%s", spaces4);
    // Only generate child tests if this is not a leaf node
    bool has_child_constraints = mList._lchild || mList._rchild;
    const char *predicate_test        = mList.get_pred();
    if( has_child_constraints || predicate_test ) {
        // Open the child-and-predicate-test braces
        fprintf(fp, "if( ");
        status.set_constraint(hasConstraint);
        child_test(fp, mList);
        // Only generate predicate test if one exists for this match
        if( predicate_test ) {
            if( has_child_constraints ) {
                fprintf(fp," &&\n");
            }
            fprintf(fp, "%s  %s", spaces6, predicate_test);
        }
        // End of outer tests
        fprintf(fp," ) ");
    } else {
        // No child or predicate test needed
        status.set_constraint(noConstraint);
    }

    // End of outer tests
    fprintf(fp,"{\n");

    // Calculate cost of this match
    const Expr *cost = calc_cost(fp, spaces6, mList, status);
    // Check against other match costs, and update cost & rule vectors
    cost_check(fp, spaces6, ArchDesc::getMachOperEnum(mList._resultStr), cost, mList._opcode, status);

    // If this is a member of an operand class, update the class cost & rule
    expand_opclass( fp, spaces6, cost, mList._resultStr, status);

    // Check if this rule should be used to generate the chains as well.
    const char *rule = /* set rule to "Invalid" for internal operands */
        strcmp(mList._opcode,mList._resultStr) ? mList._opcode : "Invalid";

    // If this rule produces an operand which has associated chain rules,
    // update the operands with the chain rule + this rule cost & this rule.
    chain_rule(fp, spaces6, mList._resultStr, cost, rule, operands_chained_from, status);

    // Close the child-and-predicate-test braces
    fprintf(fp, "    }\n");

}
Exemplo n.º 18
0
Homography autoHomography(const Image<Color>&I1,const Image<Color>&I2,bool ransac=false) {
    Detector d;
    Array<Feat> feats1=d.run(I1);
    drawFeatures(feats1);
    Array<Feat> feats2=d.run(I2);
    drawFeatures(feats2,IntPoint2(I1.width(),0));
    MatchList matches=loweMatch(feats1,feats2,.5,true);
    cout << matches.size() << " matches" << endl;
    drawMatches(feats1,feats2,matches,IntPoint2(0,0),IntPoint2(I1.width(),0),1.,true);
    click();
    if (ransac) {
        Homography H;
        double outlier_thres;
        double median_res = leastMedianOfSquares<4>(matches.begin(), matches.end(), HomEstimator(feats1,feats2), HomResidual(feats1,feats2),H,&outlier_thres);
        MatchList inliers;
        for (MatchList::const_iterator it=matches.begin(); it!=matches.end(); it++) {
            if ( HomResidual(feats1,feats2)(H,*it) < outlier_thres )
                inliers.push_front(*it);
        }
        cout << inliers.size() << " inliers" << endl;
        matches=inliers;
    }
    return meanHomography(feats1,feats2,matches);
}
Exemplo n.º 19
0
void _hammingmatchserial(const cv::Mat &des1, const cv::Mat &des2, MatchList &good, const float ratio){
	int _n1 = des1.rows;
	int dist, idx;
	cv::DMatch _mtch;
	int step = des1.step / sizeof(des1.ptr()[0]);
	for (int _i = 0; _i < _n1; ++_i){
		if ( des2.cols == 64 ){
			idx = _bestmatch64(des1.data+step*_i, des2, dist, ratio, FASTHAMMING_FREAK_PARSCORE);
		} else {
			idx = _bestmatch64(des1.data+step*_i, des2, dist, ratio);
		}
		if ( idx != -1){
			_mtch.distance = dist;
			_mtch.queryIdx = _i;
			_mtch.trainIdx = idx;
			good.push_back(_mtch);
		}
	}
}
Exemplo n.º 20
0
bool getExtractionData(const Game *g, Search &search, ExtractMap &map) {
	SearchMap searchMap;

	const int *needList = getNeedList(g);
	if (!needList) {
		fprintf(stderr, "ERROR: No entry need list available\n");
		return false;
	}

	if (!setupSearch(g, needList, search, searchMap))
		return false;

	// Process the data search
	Search::ResultList results;
	search.search(results);

	if (results.empty()) {
		fprintf(stderr, "ERROR: Couldn't find any required data\n");
		return false;
	}

	ExtractMap temporaryExtractMap;
	for (const int *entry = needList; *entry != -1; ++entry) {
		typedef std::pair<SearchMap::const_iterator, SearchMap::const_iterator> KeyRange;
		KeyRange idRange = searchMap.equal_range(*entry);

		for (Search::ResultList::const_iterator i = results.begin(); i != results.end(); ++i) {
			for (SearchMap::const_iterator j = idRange.first; j != idRange.second; ++j) {
				if (j->second.hint == i->data)
					temporaryExtractMap.insert(ExtractMapEntry(*entry, ExtractData(j->second, i->offset)));
			}
		}
	}

	// Free up some memory
	results.clear();
	searchMap.clear();

	bool result = true;

	for (const int *entry = needList; *entry != -1; ++entry) {
		MatchList possibleMatches = filterPlatformMatches(g, temporaryExtractMap.equal_range(*entry));

		if (possibleMatches.empty()) {
			fprintf(stderr, "ERROR: No entry found for id %d/%s\n", *entry, getIdString(*entry));
			result = false;
			continue;
		}

		if (isLangSpecific(*entry)) {
			for (int i = 0; i < 3; ++i) {
				if (g->lang[i] == -1)
					continue;

				MatchList langMatches = filterLanguageMatches(g->lang[i], possibleMatches);
				MatchList::const_iterator bestMatch = filterOutBestMatch(langMatches);

				if (bestMatch == langMatches.end()) {
					// TODO: Add nice language name to output message.
					fprintf(stderr, "ERROR: No entry found for id %d/%s for language %d\n", *entry, getIdString(*entry), g->lang[i]);
					result = false;
					continue;
				}

#ifdef DEBUG_EXTRACTION_TABLES
				if (((*bestMatch)->second.desc.platform != kPlatformUnknown && (*bestMatch)->second.desc.platform != g->platform))
					printf("%s: %.8X %.8X %d %d\n", getIdString(*entry), (*bestMatch)->second.desc.hint.size, (*bestMatch)->second.desc.hint.byteSum, (*bestMatch)->second.desc.lang, (*bestMatch)->second.desc.platform);
#endif

				map.insert(**bestMatch);
			}
		} else {
			MatchList::const_iterator bestMatch = filterOutBestMatch(possibleMatches);

			if (bestMatch == possibleMatches.end()) {
				fprintf(stderr, "ERROR: No entry found for id %d/%s\n", *entry, getIdString(*entry));
				result = false;
				continue;
			}

#ifdef DEBUG_EXTRACTION_TABLES
			if (((*bestMatch)->second.desc.platform != kPlatformUnknown && (*bestMatch)->second.desc.platform != g->platform))
				printf("%s: %.8X %.8X %d %d\n", getIdString(*entry), (*bestMatch)->second.desc.hint.size, (*bestMatch)->second.desc.hint.byteSum, (*bestMatch)->second.desc.lang, (*bestMatch)->second.desc.platform);
#endif

			map.insert(**bestMatch);
		}
	}

	return result;
}
Exemplo n.º 21
0
void FileScanner::ScanFileLibPCRE(const char *file_data, size_t file_size, MatchList& ml)
{
	// Match output vector.  We won't support submatches, so we only need two entries, plus a third for pcre's own use.
	int ovector[3] = {-1, 0, 0};
	long long line_no = 1;
	long long prev_lineno = 0;
	const char *prev_lineno_search_end = file_data;
	// Up-cast file_size, which is a size_t (unsigned) to a ptrdiff_t (signed) which should be able to handle the
	// same positive range, and not cause issues when compared with the ints of ovector[].
	std::ptrdiff_t signed_file_size = file_size;

	// Loop while the start_offset is less than the file_size.
	while(ovector[1] < signed_file_size)
	{
		int options = 0;
		int start_offset = ovector[1];

		// Was the previous match zero-length?
		if (ovector[0] == ovector[1])
		{
			// Yes, are we at the end of the file?
			if (ovector[0] == signed_file_size)
			{
				// Yes, we're done searching.
				break;
			}

			// Not done, set options for another try for a non-empty match at the same point.
			options = PCRE_NOTEMPTY_ATSTART | PCRE_ANCHORED;
		}

		// Try to match the regex to whatever's left of the file.
		int rc = pcre_exec(
				m_pcre_regex,
				m_pcre_extra,
				file_data,
				file_size,
				start_offset,
				options,
				ovector,
				3);

		// Check for no match.
		if(rc == PCRE_ERROR_NOMATCH)
		{
			if(options == 0)
			{
				// We weren't trying to recover from a zero-length match, so there are no more matches.
				// Break out of the loop.
				break;
			}
			else
			{
				// We've failed to find a non-empty-string match at a point where
				// we previously found an empty-string match.
				// Advance one character and continue.
				ovector[1] = start_offset + 1;

				/**
				 * @todo If we're treating \r\n as a newline, we have to check here to see
				 *       if we are at the start of one, and if so, skip over the whole thing.
				 *       For now, we don't support this.
				 */
				if(/** @todo crlf_is_newline */ false &&
						start_offset < file_size -1 &&
						file_data[start_offset] == '\r' &&
						file_data[start_offset] == '\n')
				{
					// Increment the new start position by one more byte, we're at a \r\n line ending.
					ovector[1]++;
				}
				/**
				 * @todo Similarly, if we support UTF-8, we have to skip all bytes in the
				 *       possibly multi-byte character.
				 *       Again, UTF-8 is not something we support at the moment.
				 */
				else if(false /** @todo utf8 */)
				{
					// Increment a whole UTF8 character.
					while(ovector[1] < signed_file_size)
					{
						if((file_data[ovector[1]] & 0xC0) != 0x80)
						{
							// Found a non-start-byte.
							break;
						}
						else
						{
							// Go to the next byte in the character.
							ovector[1]++;
						}
					}
				}
			}

			// Try to match again.
			continue;
		}

		// Check for non-PCRE_ERROR_NOMATCH error codes.
		if(rc < 0)
		{
			std::cerr << "ERROR: Match error " << rc << "." << std::endl;
			return;
		}
		if (rc == 0)
		{
			std::cerr << "ERROR: ovector only has room for 1 captured substring" << std::endl;
			return;
		}

		// There was a match.  Package it up in the MatchList which was passed in.
		long long num_lines_since_last_match = std::count(prev_lineno_search_end, file_data+ovector[0], '\n');
		line_no += num_lines_since_last_match;
		prev_lineno_search_end = file_data+ovector[0];
		if(line_no == prev_lineno)
		{
			// Skip multiple matches on one line.
			continue;
		}
		prev_lineno = line_no;
		Match m(file_data, file_size, ovector[0], ovector[1], line_no);

		ml.AddMatch(std::move(m));
	}
}