/*
* opencog/atoms/value/RelationalValue.cc
*
* Copyright (C) 2025 BrainyBlaze Dynamics, LLC
* 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, see <https://www.gnu.org/licenses/>.
*/
#include <opencog/atoms/value/RelationalValue.h>
#include <opencog/atomspace/AtomSpace.h>
using namespace opencog;
// ==============================================================
RelationalValue::RelationalValue(Type t, const Handle& schema)
: UnisetValue(t), _schema(schema)
{
init_schema();
}
RelationalValue::~RelationalValue()
{
// Can't leave the scratch space hanging around...
_schema->getAtomSpace()->remove_atom(HandleCast(_scratch), true);
}
// ==============================================================
// Set up the comparison machinery by wrapping the given schema in
// an ExecutionOutputLink, fed by a pair of ValueShims that will pass
// the Values into the schema.
void RelationalValue::init_schema(void)
{
// ValueShims for left and right comparison arguments
_left_shim = createValueShimLink();
_right_shim = createValueShimLink();
// The ExecutionOutputLink provides us with general machinery
// that can run the schema on the pair to be compared. The
// only gotcha here is that the shims cannot be placed in any
// AtomSpace, and thus theExOutLink can't be, either. So far,
// that's OK. There's also an annoying meta-issue: the schema
// can't be applied without beta-reduction, which is ...
// annoying. But that's the best we can do with the current
// architecture. For now.
_exout =
createLink(HandleSeq({
_schema,
createLink(HandleSeq({
HandleCast(_left_shim),
HandleCast(_right_shim)}),
LIST_LINK)}),
EXECUTION_OUTPUT_LINK);
// Scratch space in which temporaries are evaluated. It overlays
// the AtomSpace in which the schema lives, and thus the schema has
// access to this, to use as context.
_scratch = createAtomSpace(_schema->getAtomSpace());
_scratch->set_copy_on_write();
}
// ==============================================================
// Use the provided schema to compare two values.
bool RelationalValue::compare(const Value& lhs, const Value& rhs) const
{
// Set the values to be compared in the shims.
_left_shim->set_value(ValuePtr(const_cast<Value*>(&lhs), [](Value*){}));
_right_shim->set_value(ValuePtr(const_cast<Value*>(&rhs), [](Value*){}));
_scratch->clear();
// Set silent=true to catch SilentException.
return _exout->bevaluate(_scratch.get(), true);
}
// ==============================================================
// Clear the transient before each use. That way, the base
// AtomSpace always provides accurate context for the schema.
// We need to do this only once per add, and not once per
// less(). There will be, in general, log(N) calls to less for
// a SortedStream of size N. Or so one would hope. But the impl
// under the covers is std::set<> and it seems to be calling
// 2x that, because I guess it has no operator==() to work with.
/// Add one item to the stream. If the item is a VoidValue
/// or an empty LinkValue, the stream closes.
void RelationalValue::add(const ValuePtr& vp)
{
if (0 == vp->size())
{
close();
return;
}
_scratch->clear();
// The comparison relation can fail when the place to
// get data from does not exist. Although such a case
// "should not happen", and "it's the user's fault", in practice
// it seems that the reasons are benign, and we shouldn't tank
// the pipeline because of this. The most reasonable thing I can
// think of is just not add this element to the set; that is,
// if there is no data, then pretend it does not exist.
// So, catch the access exception, and skip adding it.
// This will be a SilentException; other exceptions are real
// and are not guarded against.
//
// Do above for *any* insertion, but also special-case the very
// first insertion. If the very first elt to be added is unchecked,
// and we are so unlucky that it's the one that trips the exception,
// then all subsequent adds will fail. So hand-check it.
//
if (0 == _set.size())
{
try {
compare(*vp, *vp);
} catch (const SilentException& ex) {
return;
}
}
try {
_set.insert(vp);
} catch (const SilentException& ex) {}
}
void RelationalValue::add(ValuePtr&& vp)
{
if (0 == vp->size())
{
close();
return;
}
// See notes above.
_scratch->clear();
if (0 == _set.size())
{
try {
compare(*vp, *vp);
} catch (const SilentException& ex) {
return;
}
}
try {
_set.insert(std::move(vp));
} catch (const SilentException& ex) {}
}
// ==============================================================
std::string RelationalValue::to_string(const std::string& indent) const
{
std::string rv = indent + "(" + nameserver().getTypeName(_type);
rv += "\n";
rv += _schema->to_short_string(indent + " ");
if (_source)
rv += _source->to_short_string(indent + " ");
rv += ")\n";
rv += indent + "; Currently:\n";
rv += LinkValue::to_string(indent + "; ", LINK_VALUE);
return rv;
}
// ==============================================================