/*
* opencog/atoms/free/Replacement.cc
*
* Copyright (C) 2009, 2014, 2015, 2025 Linas Vepstas
* 2019 SingularityNET Foundation
*
* Authors: Linas Vepstas <linasvepstas@gmail.com> January 2009
* Nil Geisweiller <ngeiswei@gmail.com> Oct 2019
*
* 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.
*/
#include <opencog/atoms/base/Atom.h>
#include <opencog/atoms/base/Link.h>
#include <opencog/atoms/atom_types/NameServer.h>
#include <opencog/atoms/free/Context.h>
#include <opencog/atoms/scope/ScopeLink.h>
namespace opencog {
/* ================================================================= */
Handle Replacement::replace_nocheck(const Handle& term,
const HandleMap& vm,
bool do_exec)
{
HandleSeq to_insert;
IndexMap insert_index;
size_t idx = 0;
for (const auto& pr : vm)
{
to_insert.push_back(pr.second);
insert_index.insert({pr.first, idx});
idx++;
}
return substitute_scoped(term, to_insert, insert_index, do_exec, false);
}
/* ================================================================= */
/// Perform beta-reduction on the term. This is more-or-less a purely
/// syntactic beta-reduction, except for three semantic parts:
///
/// 1. The semantics of QuoteLink, UnquoteLink is honoured, so that
/// quoted variables are not reduced.
///
/// 2. The semantics of scoping (alpha-conversion) is honored, so that
/// any scoped variables with the same name as the free variables
/// are alpha-hidden, possibly alpha-converted if the substituting
/// values are variables of the same name.
///
/// 3. Executable intermediates are executed. This is an important,
/// even central part of implementing flowing streams. However, it
/// might also be a design flaw that should be solved in some other
/// way. One key weirdness is that the intermediate results might
/// be executable atoms with wrapped Values (in ValueShimeLink).
/// These cannot be placed in any AtomSpace, and so have to be
/// executed "bare". This works, but... Hrmmmpf.
//
Handle Replacement::substitute_scoped(Handle term,
const HandleSeq& args,
const IndexMap& index_map,
bool do_exec,
const Context& context)
{
bool unquoted = context.is_unquoted();
// If we are not in a quote context, and `term` is not shadowing a
// bound (scoped) term, then just return the corresponding grounding.
if (unquoted)
{
IndexMap::const_iterator idx = index_map.find(term);
if (idx != index_map.end())
{
// Substitute if term is not a shadowing term inside a ScopeLink
if (0 == context.shadow.count(term) and
0 == context.shadow_terms.count(term))
return args.at(idx->second);
}
}
// If its a node, (and its not a variable), then it is a constant,
// and just return that.
if (not term->is_link()) return term;
// Perform alpha-conversion duck-n-cover.
if (unquoted and term->is_type(SCOPE_LINK))
{
// If a substituting value happens to be a variable in a ScopeLink,
// then alpha-convert the scope to avoid variable name
// collision. Loop in the rare case the new names collide.
while (must_alpha_convert(term, args))
term = ScopeLinkCast(term)->alpha_convert();
}
Context updated_ctxt(context);
updated_ctxt.update(term);
// Recursively fill out the subtrees.
HandleSeq oset;
bool changed = false;
for (const Handle& h : term->getOutgoingSet())
{
Handle sub(substitute_scoped(h, args, index_map, do_exec, updated_ctxt));
// GlobNodes are matched with a list of one or more arguments.
// Those arguments need to be in-lined, stripping off the list
// that wraps them up. See FilterLinkUTest for examples.
if (GLOB_NODE == h->get_type())
{
changed = true;
// Also unwrap any ListLinks that were inserted by
// `wrap_glob_with_list()` in RewriteLink.cc
// (Unordered globs get wrapped with UnorderedLink)
if (sub->get_type() == LIST_LINK or
sub->get_type() == UNORDERED_LINK)
for (const Handle &gl : sub->getOutgoingSet())
oset.emplace_back(gl);
else
oset.emplace_back(sub);
continue;
}
if (sub != h)
{
changed = true;
// End of the line for streaming data. If the arguments
// that were being plugged in were executable streams,
// but they're being placed into something that is not
// executable, then run those streams, terminating them.
// The final result is then just a plain-old ordinary
// non-executable Atom, and nothing more.
//
// Well, sort-of. Execution could return something that
// is not an Atom. In that case, we record the original
// form. This original form might be executed again,
// later on, and if this execution has side-effects,
// then, well, things get ugly. But there's no obvious
// way of avoiding this; we'd need some method that
// tells us if execution returns only Atoms and never
// Values. And we don't have such a function...
if (do_exec and
not term->is_executable() and
sub->is_executable())
{
// AtomSpace* as = term->getAtomSpace();
ValuePtr evp = sub->execute();
if (evp->is_atom())
sub = HandleCast(evp);
}
}
oset.emplace_back(sub);
}
// Return the original atom, if it was not modified.
if (not changed) return term;
return createLink(std::move(oset), term->get_type());
}
/* ================================================================= */
bool Replacement::must_alpha_convert(const Handle& scope,
const HandleSeq& args)
{
const HandleSet& vars = ScopeLinkCast(scope)->get_variables().varset;
for (const Handle& value : args)
if (vars.find(value) != vars.end())
return true;
return false;
}
/* ================================================================= */
} // ~namespace opencog