OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / atoms / parallel / PureExecLink.cc

133 lines · cc ref:
/*
 * opencog/atoms/parallel/PureExecLink.cc
 *
 * Copyright (C) 2009, 2013-2015, 2020, 2024, 2025 Linas Vepstas
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * All Rights Reserved
 *
 * 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/atoms/parallel/PureExecLink.h>
#include <opencog/atoms/value/LinkValue.h>
#include <opencog/atomspace/AtomSpace.h>

using namespace opencog;

/// PureExecLink
/// Perform execution in given AtomSpace, or a transient, if none given.
///
/// The general structure of this link is
///
///        PureExecLink
///            AtomSpace (optional)
///            ExecutableAtom
///            AnotherExecutableAtom
///            AnotherAtomSpace (optional)
///            MoreExecutableAtom
///
/// When this link is executed, all of the various `ExecutableAtoms`
/// are executed in the sequential order, in the most recent AtomSpace
/// that preceeded them. Thus, if execution has side effects, such
/// as creating new Atoms, they end up there, and not the current
/// AtomSpace. That's what make's it "Pure" -- no side-effects in the
/// current AtomSpace.
///
/// If no AtomSpace is given, a temporary transient is used.
///
/// Execution results are placed in a LinkValue; the LinkValue is
/// returned.

PureExecLink::PureExecLink(const HandleSeq&& oset, Type t)
    : Link(std::move(oset), t)
{
	if (0 == _outgoing.size())
		throw InvalidParamException(TRACE_INFO,
			"Expecting at least one argument!");
}

ValuePtr PureExecLink::execute(AtomSpace* as,
                               bool silent)
{
	ValueSeq vseq;
	AtomSpace* ctxt = nullptr;
	AtomSpacePtr scratch = nullptr;
	for (const Handle& h : _outgoing)
	{
		if (h->is_type(ATOM_SPACE))
		{
			ctxt = AtomSpaceCast(h).get();
			// Well, we could "record it for posterity", but if the
			// user only wanted to do one thing and get the result,
			// this extra labelling just gets in the way. Worse, the
			// user would almost surely discard this label, anyway.
			// If they really really need this info, they can get it
			// in other ways.
			// vseq.push_back(h); // Record it, for posterity.
			continue;
		}
		if (not h->is_executable())
		{
			vseq.push_back(h);
			continue;
		}

		// If we don't have a context to run in, create a scratch space.
		if (nullptr == ctxt)
		{
			scratch = createAtomSpace(as);
			scratch->set_copy_on_write();
			ctxt = scratch.get();
		}

		ValuePtr vp(h->execute(ctxt, silent));

		// Should not really happen except it sometimes does:
		// null pointers. Yuck. Oh well.
		if (nullptr == vp)
			continue;

		// Special case. New context.
		if (vp->is_type(ATOM_SPACE))
			ctxt = AtomSpaceCast(vp).get();
		else
			vseq.emplace_back(vp);
	}

	// The scratch space got placed into the parent. Remove it.
	// Force a recursive remove, in case even more spaces got
	// placed in there. (Does recursive remove for AtomSpaces work??)
	if (scratch.get())
	{
		scratch->clear();
		as->remove_atom(HandleCast(scratch), true);
	}

	// Sop to the user. If only one item, return that one item.
	// This feels both hacky, and also reasonable. The hacky
	// feeling is that it behaves differently, based on size. Yuck.
	// But its also natural, as why should we wrap just one thing?
	// This would force the user to unwrap, adding complexity to
	// processing pipelines. The user just wanted to "do just one
	// thing", and we did it; the extra gift-wrapping is just junk.
	if (1 == vseq.size())
		return vseq[0];

	return createLinkValue(std::move(vseq));
}

DEFINE_LINK_FACTORY(PureExecLink, PURE_EXEC_LINK)