OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / atoms / flow / FilterLink.cc

338 lines · cc ref:
/*
 * opencog/atoms/flow/FilterLink.cc
 *
 * Copyright (C) 2015, 2016, 2022 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/atomspace/AtomSpace.h>
#include <opencog/atoms/base/ClassServer.h>
#include <opencog/atoms/grant/DefineLink.h>
#include <opencog/atoms/scope/VariableSet.h>
#include <opencog/atoms/rule/RuleLink.h>
#include <opencog/atoms/value/LinkValue.h>
#include <opencog/atoms/value/ContainerValue.h>

#include "FilterLink.h"
#include "LinkSignatureLink.h"
#include "ValueShimLink.h"

using namespace opencog;

void FilterLink::init(void)
{
	_recursive_exec = false;

	// Filters consist of a function, and the data to apply the
	// function to.  The function can be explicit (inheriting from
	// ScopeLink) or implicit (we automatically fish out free variables).
	if (2 != _outgoing.size())
		throw SyntaxException(TRACE_INFO,
			"FilterLink is expected to be arity-2 only!");

	Handle termpat = _outgoing[0];
	Type tscope = termpat->get_type();

	// Expand definitions
	if (nameserver().isA(tscope, DEFINED_PROCEDURE_NODE))
	{
		termpat = DefineLink::get_definition(termpat);
		if (nullptr == termpat)
			throw SyntaxException(TRACE_INFO,
				"FilterLink cannot find definition for %s",
				_outgoing[0]->to_string().c_str());

		tscope = termpat->get_type();
	}

	// First argument must be a function of some kind.
	// We convert all functions to guarded functions,
	// first, to manage the input-variables and the beta
	// reduction with ScopeLink, and second, to do
	// type-guarding with GuardLink.
	if (nameserver().isA(tscope, GUARD_LINK))
	{
		_guard_ptrn = GuardLinkCast(termpat);
	}
	else
	{
		FreeVariables fv;
		fv.find_variables(termpat);
		Handle decl(createVariableSet(std::move(fv.varseq)));
		_guard_ptrn = createGuardLink(HandleSeq{decl, termpat});
	}
	_mvars = &_guard_ptrn->get_variables();

	// RuleLinks are a special type of ScopeLink.  They specify a
	// re-write that should be performed.  Viz, RuleLinks are
	// of the form P(x)->Q(x).  Here, the `_rewrite` is the Q(x)
	if (nameserver().isA(tscope, RULE_LINK))
		_rewrite = RuleLinkCast(HandleCast(_guard_ptrn))->get_implicand();
}

FilterLink::FilterLink(const Handle& pattern, const Handle& term)
	: FunctionLink(HandleSeq({pattern, term}), FILTER_LINK)
{
	init();
}

FilterLink::FilterLink(Type t, const Handle& body)
	: FunctionLink(HandleSeq({body}), t)
{
	// Derived types have a different initialization sequence.
	if (FILTER_LINK != t) return;
	init();
}

FilterLink::FilterLink(const HandleSeq&& oset, Type t)
	: FunctionLink(std::move(oset), t)
{
	if (not nameserver().isA(t, FILTER_LINK))
	{
		const std::string& tname = nameserver().getTypeName(t);
		throw SyntaxException(TRACE_INFO,
			"Expecting a FilterLink, got %s", tname.c_str());
	}

	// Derived types have a different initialization sequence.
	if (FILTER_LINK != t) return;
	init();
}

// ====================================================================

ValuePtr FilterLink::rewrite_one(const ValuePtr& vterm,
                                 AtomSpace* scratch, bool silent) const
{
	// See if the term passes pattern matching. If it does, the
	// side effect is that we get a grounding map as output.
	ValueMap valmap;
	if (not _guard_ptrn->guard(vterm, valmap, scratch, silent))
		return Handle::UNDEFINED;

	// Special case for signatures. The extract already rejected
	// mis-matches, if any. Thus, we are done, here.
	const Handle& body(_guard_ptrn->get_body());
	if (body->is_type(TYPE_NODE) or
	    (body->is_type(TYPE_OUTPUT_SIG) and
	       (not body->is_type(LINK_SIGNATURE_LINK))))
		return vterm;

	// If there is no RuleLink to fire, then just wrap up the results
	// and return them.
	if (_rewrite.empty())
	{
		if (1 == valmap.size()) return valmap.begin()->second;

		// Multiple Values to return. Two generic cases: the return
		// value is a set of Atoms, or a set of non-Atom Values.
		if (body->is_type(LINK_SIGNATURE_LINK))
		{
			ValueSeq valseq;
			for (const Handle& var : _mvars->varseq)
			{
				const auto& valpair = valmap.find(var);
				valseq.emplace_back(valpair->second);
			}
			return LinkSignatureLinkCast(body)->construct(scratch, std::move(valseq));
		}

		// A list of Handles.
		HandleSeq valseq;
		for (const Handle& var : _mvars->varseq)
		{
			const auto& valpair = valmap.find(var);
			if (valmap.end() == valmap.find(var))
				throw SyntaxException (TRACE_INFO,
					"Malformed FilterLink; no value for %s in %s",
					to_string().c_str(), var->to_string().c_str());

			valseq.emplace_back(HandleCast(valpair->second));
		}
		return scratch->add_link(LIST_LINK, std::move(valseq));
	}

	// If we are there, then there's a rule to fire. Two generic
	// cases to be handled:
	// 1) If the rewrite rule is not executable, then the grounding
	//    must consist of Atoms only. The grounding is plugged into
	//    the rewrite, i.e. beta-reduced.
	// 2) If the rewrite rule is executable, then the grounding
	//    is handed to that, without any further ado. The grounding
	//    might be either Values or Atoms; in either case, they're
	//    arguments for the function to be executed.

	// Place the groundings into a sequence, for easy access.
	HandleSeq valseq;
	for (const Handle& var : _mvars->varseq)
	{
		auto valpair = valmap.find(var);

		// This can happen in some unusual cases. So don't complain.
		if (valmap.end() == valpair)
			throw FatalErrorException(TRACE_INFO,
			     "Internal error; bug in filtering code");
			// continue;

		const ValuePtr& gnding(valpair->second);

		if (gnding->is_atom())
			valseq.emplace_back(HandleCast(gnding));
		else
			valseq.emplace_back(createValueShimLink(gnding));
	}

	ValueSeq rew;
	// Beta reduce, and execute. No type-checking during
	// beta-reduction; we've already done that, during matching.
	for (const Handle& impl : _rewrite)
	{
		ValuePtr red(_mvars->substitute_nocheck(impl, valseq, true));
		if (red->is_atom())
		{
			const Handle& hred(HandleCast(red));
			if (hred->is_executable())
			{
				ValuePtr v(hred->execute(scratch, silent));
				if (v)
				{
					if (v->is_atom())
						rew.emplace_back(scratch->add_atom(HandleCast(v)));
					else
						rew.emplace_back(v);
				}
			}
			else
				rew.emplace_back(scratch->add_atom(hred));
		}
		else
			rew.emplace_back(red);
	}

	if (1 == rew.size()) return rew[0];

	bool have_vals = false;
	for (const ValuePtr& v : rew)
		if (v and not v->is_atom()) { have_vals = true; break; }

	// Multiple Values to return. Two generic cases: the return
	// value is a set of Atoms, or a set of non-Atom Values.
	if (have_vals or body->is_type(LINK_SIGNATURE_LINK))
	{
		// Type kind = LinkSignatureLinkCast(body)->get_kind();
		// if (LINK_VALUE == kind) ...
		return createLinkValue(std::move(rew));
	}

	// A list of Handles.
	HandleSeq hseq;
	for (const ValuePtr& v : rew) hseq.emplace_back(HandleCast(v));
	return scratch->add_link(LIST_LINK, std::move(hseq));
}

ValuePtr FilterLink::do_execute(AtomSpace* as, bool silent) const
{
	ValuePtr vex(_outgoing[1]);

	if (_outgoing[1]->is_executable())
	{
		vex = _outgoing[1]->execute(as, silent);

		// If it's a container, and its not closed, then pull
		// one value out, and process it. Else if its closed,
		// fall through, and let the next stage handle it.
		if (vex->is_type(CONTAINER_VALUE))
		{
			ContainerValuePtr cvp = ContainerValueCast(vex);
			if (not cvp->is_closed())
			{
				ValuePtr mone = rewrite_one(cvp->remove(), as, silent);
				return createLinkValue(mone);
			}
			// If it is closed, fall through.
		}

		if (vex->is_type(LINK_VALUE))
		{
			ValueSeq remap;
			for (const ValuePtr& vp : LinkValueCast(vex)->value())
			{
				ValuePtr mone = rewrite_one(vp, as, silent);
				if (nullptr != mone) remap.emplace_back(mone);
			}
			return createLinkValue(std::move(remap));
		}
	}

	// Handle four different cases.
	// If there is a single Atom, apply the filter to the single Atom.
	// If there is a Set of Atoms, apply the filter to the set members.
	// If there is a List of Atoms, apply the filter to the list elts.
	// If there is a LinkValue, apply the filter to its elts.
	Type argtype = vex->get_type();
	if (SET_LINK == argtype or LIST_LINK == argtype)
	{
		Handle valh(HandleCast(vex));
		HandleSeq remap;
		for (const Handle& h : valh->getOutgoingSet())
		{
			Handle mone = HandleCast(rewrite_one(h, as, silent));
			if (nullptr != mone) remap.emplace_back(mone);
		}
		return as->add_link(argtype, std::move(remap));
	}

	// If we're getting a shim, we MUST unwrap it. If the shim
	// wraps a LinkValue, iterate over that.
	if (VALUE_SHIM_LINK == argtype)
	{
		Handle valh(HandleCast(vex));
		ValuePtr svp(valh->execute(as, silent));
		if (LINK_VALUE != svp->get_type()) return svp;

		ValueSeq remap;
		const ValueSeq& vsq(LinkValueCast(svp)->value());
		for (const ValuePtr& v: vsq)
			remap.emplace_back(rewrite_one(v, as, silent));

		if (1 == remap.size()) return remap[0];
		return createLinkValue(std::move(remap));
	}

	// Its a singleton. Just remap that.
	return rewrite_one(vex, as, silent);
}

ValuePtr FilterLink::execute(AtomSpace* as, bool silent)
{
	// This can execute recursively, if the AtomSpace is searched for
	// FilterLinks, and then the results passed through a FilterLink.
	// This is "rare", but the other outcome is stack exhaustion and
	// crash.
	if (_recursive_exec)
		return get_handle();

	_recursive_exec = true;
	ValuePtr vp(do_execute(as, silent));
	_recursive_exec = false;
	return vp;
}

DEFINE_LINK_FACTORY(FilterLink, FILTER_LINK)

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