OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / query / ContinuationMixin.cc

169 lines · cc ref:
/*
 * ContinuationMixin.cc
 *
 * Copyright (C) 2021 Linas Vepstas
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License v3 as
 * published by the Free Software Foundation and including the exceptions
 * at http://opencog.org/wiki/Licenses
 *
 * This program 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 Affero General Public License
 * along with this program; if not, write to:
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <opencog/util/exceptions.h>
#include <opencog/util/Logger.h>

#include <opencog/atoms/free/Replacement.h>

#include "ContinuationMixin.h"

using namespace opencog;

// #define QDEBUG 1
#ifdef QDEBUG
#define DO_LOG(STUFF) STUFF
#else
#define DO_LOG(STUFF)
#endif


/**
 * Exception thrown for continuations
 */
class ContinuationException : public SilentException
{
public:
    ContinuationException(void) {}
};

class RewindException : public SilentException
{
public:
    RewindException(void) {}
};

/* ======================================================== */

/**
 */
bool ContinuationMixin::evaluate_sentence(const Handle& top,
                                          const GroundingMap& gnds)
{
	if (CONTINUATION_LINK == top->get_type())
	{
		_continuation = Replacement::replace_nocheck(top, gnds);
		throw ContinuationException();
	}
	return TermMatchMixin::evaluate_sentence(top, gnds);
}

// Assorted thread-local state. This is used to determine if we
// are entering the query engine for the first time ever, or if
// we are recursing.
static thread_local bool in_continuation = false;
static thread_local PatternLinkPtr localpat = nullptr;

bool ContinuationMixin::satisfy(const PatternLinkPtr& form)
{
	// If we are in_continuation, then this is not the first time we
	// were called. Record the pattern to be grounded, and throw an
	// exception to get us back to the first, base call.
	if (in_continuation)
	{
		localpat = form;
		throw RewindException();
	}

	PatternLinkPtr lform = form;
	int cnt = 0;

	// The exception thrown immediately above will be caught near the
	// very end of this while-loop. The loop just brings us back to the
	// beginning, with lform set to localpat.
	while (true)
	{
		// Wrap the actual satisfier in a try-catch loop. If the pattern
		// has a ContinuationLink in it, and that link is hit, then the
		// evaluate_sentence() above will throw. It records the grounding
		// that it has found. Here, we catch, take that grounding, and
		// evaluate it.
		try
		{
			return SatisfyMixin::satisfy(lform);
		}
		catch (const ContinuationException& ex) {}

		// Temporary safety mechanism. I suspect that most users do not
		// intend to write infinite loops (e.g. REPL loops) so assume
		// infinite recursion is a user error.
		cnt++;
		if (200 < cnt)
			throw InvalidParamException(TRACE_INFO,
				"Continuation looped %d times.\n"
				"Suspect an infinite continuation loop! Are you sure?\n%s\n",
				cnt, lform->to_short_string().c_str());

		DO_LOG({LAZY_LOG_FINE << "Continue cnt=" << cnt
			<< " evaluate ContinuationLink:\n"
			<< _continuation->to_short_string(); })

		// OK. If we are here, then a ContinuationLink was seen earlier in
		// the evaluation. Convert it into an ordinary PutLink, and evaluate
		// it. If, during evaluation, someone calls this method that we are
		// in right now, i.e. if they call ContinuationMixin::satisfy(), then
		// up above, we record the PatterLink to be grounded, (recording it
		// in localpat), and up above we throw. That throw is caught below,
		// thus popping the C++ stack back to here. We can now do that
		// pattern, simply by using the goto to get back to the start.
		// So .. no change in stack frame depth, no leakage of malloc'ed
		// data. We're very very good.
		try
		{
			in_continuation = true;
			Handle plk = _continuation->getOutgoingAtom(0);
			AtomSpace* tas = TermMatchMixin::_temp_aspace.get();
			tas->clear();
			bool crispy = plk->bevaluate(tas);

			DO_LOG({LAZY_LOG_FINE << "Finish continuing, cnt=" << cnt
				<< " result=" << crispy; })

			cnt = 0;
			in_continuation = false;
			if (crispy)
			{
				GroundingMap empty;
				propose_grounding(empty, empty);
				return search_finished(false);
			}
			return false;
		}
		catch (const RewindException& ex) {}

		DO_LOG({LAZY_LOG_FINE
			<< "***************************************";})
		DO_LOG({LAZY_LOG_FINE
			<< "Enter ContinuationMixin::satisfy cnt=" << cnt;})

		// If we are here, then the exception was caught. Make note of the
		// pattern we're supposed to ground, jump up to the top, and ground
		// it.
		in_continuation = false;
		lform = localpat;
	}

	// Not reached, obviously.
	return true;
}

/* ===================== END OF FILE ===================== */