/*
* Commands.cc
* Fast command interpreter for basic AtomSpace commands.
*
* Copyright (C) 2020 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 <time.h>
#include <functional>
#include <iomanip>
#include <string>
#include <opencog/atoms/atom_types/NameServer.h>
#include <opencog/atoms/base/Link.h>
#include <opencog/atoms/base/Node.h>
#include <opencog/atoms/value/FloatValue.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/atomspace/version.h>
#include <opencog/persist/sexpr/Sexpr.h>
#include <opencog/persist/proxy/ProxyNode.h>
#include <opencog/persist/storage/storage_types.h>
#include "Commands.h"
using namespace opencog;
/// The cogserver provides a network API to send/receive Atoms over
/// the internet. The actual API is that of the StorageNode (see the
/// wiki page https://wiki.opencog.org/w/StorageNode for details.)
/// The cogserver supports the full `StorageNode` API, and it uses
/// the code in this directory in order to make it fast.
///
/// To aid in performance, a very special set of about 15 scheme
/// functions have been hard-coded in C++, in the static function
/// `Commands::interpret_command()` below. The goal is to avoid the
/// overhead of entry/exit into guile. This works because the cogserver
/// is guaranteed to send only these commands, and no others.
//
Commands::Commands(void) : _multi_space(false) {}
Commands::~Commands() {}
/// Search for optional AtomSpace argument in `cmd` at `pos`.
/// If none is found, then return `as`
AtomSpace*
Commands::get_opt_as(const std::string& cmd, size_t& pos)
{
if (not _multi_space) return _base_space.get();
pos = cmd.find_first_not_of(" \n\t", pos);
// If no optional AtomSpace, just return the base.
if (cmd.compare(pos, 10, "(AtomSpace"))
return _base_space.get();
// Ah! Optional AtomSpace! Try to handle it!
_multi_space = true;
Handle hasp = Sexpr::decode_frame(
HandleCast(_top_space), cmd, pos, _space_map);
return (AtomSpace*) hasp.get();
}
// ==================================================================
void Commands::set_base_space(const AtomSpacePtr& asp)
{
_base_space = asp;
}
// -----------------------------------------------
// (cog-atomspace)
std::string Commands::cog_atomspace(const std::string& arg)
{
if (not _top_space) return "()";
return _top_space->to_string("");
}
// -----------------------------------------------
// (cog-atomspace-clear)
std::string Commands::cog_atomspace_clear(const std::string& arg)
{
_base_space->clear();
return "#t";
}
// -----------------------------------------------
// (cog-set-proxy! (ProxyNode "foo"))
std::string Commands::cog_set_proxy(const std::string& cmd)
{
// If there already is one, do nothing.
if (_proxy) return "#f";
size_t pos = 0;
Handle h = Sexpr::decode_atom(cmd, pos, _space_map);
// If it's not a proxy, its an error.
if (not h->is_type(PROXY_NODE))
return "#f";
// Record the proxy.
h = _base_space->add_atom(h);
_proxy = ProxyNodeCast(h);
return "#t";
}
// -----------------------------------------------
// (cog-proxy-open)
std::string Commands::cog_proxy_open(const std::string& arg)
{
if (nullptr == _proxy) return "#f";
_proxy->open();
if (_proxy->connected())
return "#t";
return "#f";
}
// -----------------------------------------------
// (cog-proxy-close)
std::string Commands::cog_proxy_close(const std::string& arg)
{
if (nullptr == _proxy) return "#f";
_proxy->close();
_proxy = nullptr;
return "#t";
}
// -----------------------------------------------
// (cog-execute-cache! (GetLink ...) (Predicate "key") ...)
// This is complicated, and subject to change...
// XXX this should be nuked, and replaced by appropriate kind of proxy.
// FIXME read above comment.
std::string Commands::cog_execute_cache(const std::string& cmd)
{
size_t pos = 0;
Handle query = Sexpr::decode_atom(cmd, pos, _space_map);
query = _base_space->add_atom(query);
Handle key = Sexpr::decode_atom(cmd, ++pos, _space_map);
key = _base_space->add_atom(key);
bool force = false;
pos = cmd.find_first_of('(', pos);
if (std::string::npos != pos)
{
Handle meta = Sexpr::decode_atom(cmd, pos, _space_map);
meta = _base_space->add_atom(meta);
// XXX Hacky .. store time in float value...
_base_space->set_value(query, meta, createFloatValue((double)time(0)));
if (std::string::npos != cmd.find("#t", pos))
force = true;
}
ValuePtr rslt = query->getValue(key);
if (nullptr != rslt and not force)
return Sexpr::encode_value(rslt);
// Run the query.
if (query->is_executable())
rslt = query->execute(_base_space.get());
else if (query->is_evaluatable())
rslt = query->evaluate(_base_space.get());
else
return "#f";
_base_space->set_value(query, key, rslt);
// XXX is this correct???
// _proxy->store_value(query, key);
return Sexpr::encode_value(rslt);
}
// -----------------------------------------------
// (cog-get-atoms 'Node #t)
std::string Commands::cog_get_atoms(const std::string& cmd)
{
size_t pos = 0;
Type t = Sexpr::decode_type(cmd, pos);
pos = cmd.find_first_not_of(") \n\t", pos);
bool get_subtypes = false;
if (std::string::npos != pos and cmd.compare(pos, 2, "#f"))
get_subtypes = true;
if (_proxy and _proxy->have_loadType)
{
_proxy->fetch_all_atoms_of_type(t);
if (get_subtypes)
{
for (Type st = t+1; st < nameserver().getNumberOfClasses(); st++)
{
if (nameserver().isA(st, t))
_proxy->fetch_all_atoms_of_type(st);
}
}
_proxy->barrier();
}
// as = get_opt_as(cmd, pos, as);
std::string rv = "(";
HandleSeq hset;
if (_multi_space and _top_space)
_top_space->get_handles_by_type(hset, t, get_subtypes);
else
_base_space->get_handles_by_type(hset, t, get_subtypes);
for (const Handle& h: hset)
rv += Sexpr::encode_atom(h, _multi_space);
rv += ")";
return rv;
}
// -----------------------------------------------
// (cog-incoming-by-type (Concept "foo") 'ListLink)
std::string Commands::cog_incoming_by_type(const std::string& cmd)
{
size_t pos = 0;
Handle h = Sexpr::decode_atom(cmd, pos, _space_map);
pos++; // step past close-paren
Type t = Sexpr::decode_type(cmd, pos);
AtomSpace* as = get_opt_as(cmd, pos);
h = as->add_atom(h); // XXX shouldn't this be get_atom!????
if (_proxy and _proxy->have_fetchIncomingByType)
{
_proxy->fetch_incoming_by_type(h, t);
_proxy->barrier();
}
std::string alist = "(";
for (const Handle& hi : h->getIncomingSetByType(t))
alist += Sexpr::encode_atom(hi);
alist += ")";
return alist;
}
// -----------------------------------------------
// (cog-incoming-set (Concept "foo"))
std::string Commands::cog_incoming_set(const std::string& cmd)
{
size_t pos = 0;
Handle h = Sexpr::decode_atom(cmd, pos, _space_map);
AtomSpace* as = get_opt_as(cmd, pos);
if (_proxy and _proxy->have_fetchIncomingSet)
{
h = _proxy->fetch_incoming_set(h, false, as);
_proxy->barrier();
}
else
h = as->get_atom(h);
std::string alist = "(";
for (const Handle& hi : h->getIncomingSet())
alist += Sexpr::encode_atom(hi);
alist += ")";
return alist;
}
// -----------------------------------------------
// (cog-keys->alist (Concept "foo"))
std::string Commands::cog_keys_alist(const std::string& cmd)
{
size_t pos = 0;
Handle h = Sexpr::decode_atom(cmd, pos, _space_map);
AtomSpace* as = get_opt_as(cmd, pos);
h = as->add_atom(h); // XXX shouldn't this be get_atom!????
if (_proxy and _proxy->have_getAtom)
{
_proxy->fetch_atom(h);
_proxy->barrier();
}
std::string alist = "(";
for (const Handle& key : h->getKeys())
{
alist += "(" + Sexpr::encode_atom(key) + " . ";
alist += Sexpr::encode_value(h->getValue(key)) + ")";
}
alist += ")";
return alist;
}
// -----------------------------------------------
// (cog-node 'Concept "foobar")
std::string Commands::cog_node(const std::string& cmd)
{
size_t pos = 0;
Type t = Sexpr::decode_type(cmd, pos);
size_t l = pos+1;
size_t r = cmd.size();
std::string name = Sexpr::get_node_name(cmd, l, r, t);
std::string nam = name;
Handle h = createNode(t, std::move(nam));
// ?????? XXX Is this right? Needs review
if (_proxy and _proxy->have_getAtom)
{
_proxy->fetch_atom(h);
_proxy->barrier();
}
AtomSpace* as = get_opt_as(cmd, r);
h = as->get_node(t, std::move(name));
if (nullptr == h) return "#f\n";
return Sexpr::encode_atom(h, _multi_space);
}
// -----------------------------------------------
// (cog-link 'ListLink (Atom) (Atom) (Atom))
std::string Commands::cog_link(const std::string& cmd)
{
size_t pos = 0;
Type t = Sexpr::decode_type(cmd, pos);
HandleSeq outgoing;
size_t l = pos+1;
size_t r = cmd.size();
while (l < r and ')' != cmd[l])
{
size_t l1 = l;
size_t r1 = r;
Sexpr::get_next_expr(cmd, l1, r1, 0);
if (l1 == r1) break;
outgoing.push_back(Sexpr::decode_atom(cmd, l1, r1, 0, _space_map));
l = r1 + 1;
pos = r1;
}
HandleSeq oset = outgoing;
Handle h = createLink(std::move(oset), t);
// ?????? XXX Is this right? Needs review
if (_proxy and _proxy->have_getAtom)
{
_proxy->fetch_atom(h);
_proxy->barrier();
}
AtomSpace* as = get_opt_as(cmd, pos);
h = as->get_link(t, std::move(outgoing));
if (nullptr == h) return "#f\n";
return Sexpr::encode_atom(h, _multi_space);
}
// -----------------------------------------------
// (cog-value (Concept "foo") (Predicate "key"))
std::string Commands::cog_value(const std::string& cmd)
{
size_t pos = 0;
Handle atom = Sexpr::decode_atom(cmd, pos, _space_map);
Handle key = Sexpr::decode_atom(cmd, ++pos, _space_map);
AtomSpace* as = get_opt_as(cmd, pos);
atom = as->add_atom(atom); // XXX shouldn't this be get_atom!????
key = as->add_atom(key);
if (_proxy and _proxy->have_loadValue)
{
_proxy->fetch_value(atom, key, as);
_proxy->barrier();
}
ValuePtr vp = atom->getValue(key);
return Sexpr::encode_value(vp);
}
// -----------------------------------------------
// (cog-extract! (Concept "foo"))
std::string Commands::cog_extract(const std::string& cmd)
{
size_t pos = 0;
Handle h = _base_space->get_atom(Sexpr::decode_atom(cmd, pos, _space_map));
if (nullptr == h) return "#t";
if (_proxy and _proxy->have_removeAtom)
{
_proxy->remove_atom(_base_space, h, false);
return "#t";
}
if (_base_space->extract_atom(h, false)) return "#t";
return "#f";
}
// -----------------------------------------------
// (cog-extract-recursive! (Concept "foo"))
std::string Commands::cog_extract_recursive(const std::string& cmd)
{
size_t pos = 0;
Handle h =_base_space->get_atom(Sexpr::decode_atom(cmd, pos, _space_map));
if (nullptr == h) return "#t";
if (_proxy and _proxy->have_removeAtom)
{
_proxy->remove_atom(_base_space, h, true);
return "#t";
}
if (_base_space->extract_atom(h, true)) return "#t";
return "#f";
}
// -----------------------------------------------
// (cog-set-value! (Concept "foo") (Predicate "key") (FloatValue 1 2 3))
std::string Commands::cog_set_value(const std::string& cmd)
{
size_t pos = 0;
Handle atom = Sexpr::decode_atom(cmd, pos, _space_map);
Handle key = Sexpr::decode_atom(cmd, ++pos, _space_map);
ValuePtr vp = Sexpr::decode_value(cmd, ++pos);
AtomSpace* as = get_opt_as(cmd, pos);
atom = as->add_atom(atom);
key = as->add_atom(key);
if (vp)
vp = Sexpr::add_atoms(as, vp);
as->set_value(atom, key, vp);
if (_proxy and _proxy->have_storeValue)
_proxy->store_value(atom, key);
return "";
}
// -----------------------------------------------
// (cog-set-values! (Concept "foo") (AtomSpace "foo")
// (alist (cons (Predicate "bar") (FloatValue 0.9 0.8)) ...))
std::string Commands::cog_set_values(const std::string& cmd)
{
size_t pos = 0;
Handle h = Sexpr::decode_atom(cmd, pos, _space_map);
pos++; // skip past close-paren
if (not _multi_space)
{
// Search for optional AtomSpace argument
AtomSpace* as = get_opt_as(cmd, pos);
h = as->add_atom(h);
}
Sexpr::decode_slist(h, cmd, pos);
// TODO: In principle, we should be selective, and only pass
// on the values we were given... this would require
// Sexpr::decode_slist to return a list of keys, and then we'd
// have to store one key at a time, which seems inefficient.
// But still ... maybe fixme?
if (_proxy and _proxy->have_storeAtom)
_proxy->store_atom(h);
return "";
}
// -----------------------------------------------
// (cog-update-value! (Concept "foo") (Predicate "key") (FloatValue 1 2 3))
std::string Commands::cog_update_value(const std::string& cmd)
{
size_t pos = 0;
Handle atom = Sexpr::decode_atom(cmd, pos, _space_map);
Handle key = Sexpr::decode_atom(cmd, ++pos, _space_map);
ValuePtr vp = Sexpr::decode_value(cmd, ++pos);
AtomSpace* as = get_opt_as(cmd, pos);
atom = as->add_atom(atom);
key = as->add_atom(key);
if (not nameserver().isA(vp->get_type(), FLOAT_VALUE))
return "";
FloatValuePtr fvp = FloatValueCast(vp);
as->increment_count(atom, key, fvp->value());
if (_proxy and _proxy->have_updateValue)
_proxy->update_value(atom, key, vp);
// Return the new value. XXX Why? This just wastes CPU?
// ValuePtr vp = atom->getValue(key);
// return Sexpr::encode_value(vp);
return "";
}
// -----------------------------------------------
// (define sym (AtomSpace "foo" (AtomSpace "bar") (AtomSpace "baz")))
// Place the current atomspace at the bottom of the hierarchy.
std::string Commands::cog_define(const std::string& cmd)
{
_multi_space = true;
// Extract the symbolic name after the define
size_t pos = 0;
pos = cmd.find_first_not_of(" \n\t", pos);
size_t epos = cmd.find_first_of(" \n\t", pos);
// std::string sym = cmd.substr(pos, epos-pos);
pos = epos+1;
// Decode the AtomSpace frames
Handle hasp = Sexpr::decode_frame(
HandleCast(_base_space), cmd, pos, _space_map);
_top_space = AtomSpaceCast(hasp);
// Hacky...
// _space_map.insert({sym, top_space});
return "";
}
// -----------------------------------------------
// (ping) -- network ping
std::string Commands::cog_ping(const std::string& cmd)
{
return "()";
}
// -----------------------------------------------
// (cog-version) -- AtomSpace version
std::string Commands::cog_version(const std::string& cmd)
{
return ATOMSPACE_VERSION_STRING;
}
// ===================================================================