/*
* lookup-atomese.cc
*
* Implement the word-lookup callbacks
*
* Copyright (c) 2022 Linas Vepstas <linasvepstas@gmail.com>
*/
#ifdef HAVE_ATOMESE
#include <cstdlib>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/persist/api/StorageNode.h>
#include <opencog/persist/storage/storage_types.h>
#include <opencog/persist/cog-simple/CogSimpleStorage.h>
#include <opencog/persist/cog-storage/CogStorage.h>
#include <opencog/persist/file/FileStorage.h>
#include <opencog/persist/monospace/MonoStorage.h>
#include <opencog/persist/rocks/RocksStorage.h>
#include <opencog/persist/sexpr/Sexpr.h>
#include <opencog/lg/types/atom_types.h>
#include <opencog/util/oc_assert.h>
#include <opencog/util/Logger.h>
#undef STRINGIFY
extern "C" {
#include "../link-includes.h" // For Dictionary
#include "../api-structures.h" // For Sentence_s
#include "../dict-common/dict-common.h" // for Dictionary_s
#include "../dict-common/dict-internals.h" // for dict_node_new()
#include "../dict-common/dict-utils.h" // for size_of_expression()
#include "../dict-ram/dict-ram.h"
#include "../tokenize/word-structures.h" // for Word_struct
#include "lookup-atomese.h"
};
#include "dict-atomese.h"
#include "local-as.h"
using namespace opencog;
// Strings we expect to find in the dictionary.
#define STORAGE_NODE_STRING "storage-node"
#define COST_KEY_STRING "cost-key"
#define COST_INDEX_STRING "cost-index"
#define COST_CUTOFF_STRING "cost-cutoff"
#define COST_DEFAULT_STRING "cost-default"
#define COST_SCALE_STRING "cost-scale"
#define COST_OFFSET_STRING "cost-offset"
#define PAIR_PREDICATE_STRING "pair-predicate"
#define PAIR_KEY_STRING "pair-key"
#define PAIR_INDEX_STRING "pair-index"
#define PAIR_FORMULA_STRING "pair-formula"
#define PAIR_CUTOFF_STRING "pair-cutoff"
#define PAIR_DEFAULT_STRING "pair-default"
#define PAIR_SCALE_STRING "pair-scale"
#define PAIR_OFFSET_STRING "pair-offset"
#define ANY_DEFAULT_STRING "any-default"
#define ENABLE_SECTIONS_STRING "enable-sections"
#define EXTRA_PAIRS_STRING "extra-pairs"
#define EXTRA_ANY_STRING "extra-any"
#define PAIR_DISJUNCTS_STRING "pair-disjuncts"
#define PAIR_WITH_ANY_STRING "pair-with-any"
#define ANY_DISJUNCTS_STRING "any-disjuncts"
#define ENABLE_UNKNOWN_WORD_STRING "enable-unknown-word"
/// Shared global
static AtomSpacePtr external_atomspace;
static StorageNodePtr external_storage;
/// Specify an AtomSpace, and optionally, a StorageNode that will be
/// used to fetch dictionary contents. This should be done before
/// opening the dictionary; the values passed here are grabbed by the
/// dictionary when it is opened.
void lg_config_atomspace(AtomSpacePtr asp, StorageNodePtr sto)
{
external_atomspace = asp;
external_storage = sto;
}
static const char* get_dict_define(Dictionary dict, const char* namestr)
{
const char* val_str =
linkgrammar_get_dict_define(dict, namestr);
if (nullptr == val_str) return nullptr;
// Brute-force unescape quotes. Simple, dumb.
char* unescaped = (char*) alloca(strlen(val_str)+1);
const char* p = val_str;
char* q = unescaped;
while (*p) { if ('\\' != *p) { *q = *p; q++; } p++; }
*q = 0x0;
return string_set_add(unescaped, dict->string_set);
}
static const char* ldef(Dictionary dict, const char* name, const char* def)
{
const char* str = linkgrammar_get_dict_define(dict, name);
if (str) return str;
prt_error("Warning: missing `%s` in config; default to %s\n",
name, def);
return def;
}
/// Open a connection to a StorageNode.
bool as_open(Dictionary dict)
{
Local* local = new Local;
local->stnp = nullptr;
local->pair_dict = nullptr;
// If an external atomspace is specified, then use that.
if (external_atomspace)
{
lgdebug(D_USER_BASIC, "Atomese: Attach external AtomSpace: `%s`\n",
external_atomspace->get_name().c_str());
local->using_external_as = true;
local->asp = external_atomspace;
if (external_storage)
{
Handle hsn = local->asp->add_atom(Handle(external_storage));
local->stnp = StorageNodeCast(hsn);
if (nullptr == local->stnp)
{
prt_error("Error: Not a StorageNode! %s\n", hsn->to_string().c_str());
return false;
}
lgdebug(D_USER_BASIC, "Atomese: Using storage %s\n",
hsn->to_short_string().c_str());
}
else
lgdebug(D_USER_BASIC, "Atomese: No external storage specified.\n");
}
else
{
lgdebug(D_USER_BASIC, "Atomese: Using private Atomspace\n");
local->using_external_as = false;
local->asp = createAtomSpace();
const char * stns = get_dict_define(dict, STORAGE_NODE_STRING);
// A local AtomSpace means that there *must* be local storage,
// too, else there is no where to get dict data from. The grand
// exception would be "any" parsing, with random parse trees,
// which doesn't require a dictionary. So issue a strong warning,
// but not an error.
if (stns)
{
Handle hsn = Sexpr::decode_atom(stns);
hsn = local->asp->add_atom(hsn);
local->stnp = StorageNodeCast(hsn);
if (nullptr == local->stnp)
{
prt_error("Error: Not a StorageNode! %s\n", stns);
return false;
}
}
else
prt_error(
"Warning: Using a private AtomSpace with no StorageNode!\n"
"Are you sure?\n"
"All parses will be random planar parses using the ANY link.\n"
"This is probably not what you wanted!\n");
}
// Create the connector predicate.
// This will be used to avoid accidental aliasing.
local->bany = local->asp->add_node(BOND_NODE, "ANY");
// Internal-use only. Do we have this pair yet?
local->prk = local->asp->add_node(PREDICATE_NODE, "*-fetched-pair-*");
// Internal-use only. What is the last ID we've issued?
local->idanch = local->asp->add_node(ANCHOR_NODE, "*-LG-issued-link-id-*");
local->last_id = 0;
// -----------------------------------------------------
// Rationale for default values for costs:
//
// Word-pair MI's only "make sense" when they are 4.0 or greater.
// The highest-possible MI depends on the dataset, but is roughly
// equal to log_2 of the number of word-pairs in the dataset. The
// MI for most "good" word-pairs will be in the 4 to 20 range.
//
// Multiplying by -0.25 gives them a cost of -1.0 or more negative.
// Setting the offset to 3.0 gives them a cost of 2.0 or less.
// This maps the MI interval [4,12] to [2,0] with MI's larger than
// 12 being mapped to negative costs (thus encouraging cycles).
//
// Setting the cutoff to 2.0 causes MI of less than 4 to be dropped.
// Setting the default assigns a default cost to pairs without MI.
#define LDEF(NAME,DEF) ldef(dict, NAME, DEF)
local->enable_sections = atoi(LDEF(ENABLE_SECTIONS_STRING, "1"));
if (local->enable_sections)
{
const char* miks = ldef(dict, COST_KEY_STRING,
"(Predicate \"*-Mutual Info Key cover-section\")");
Handle mikh = Sexpr::decode_atom(miks);
local->miks = local->asp->add_atom(mikh);
local->cost_index = atoi(LDEF(COST_INDEX_STRING, "1"));
local->cost_cutoff = atof(LDEF(COST_CUTOFF_STRING, "4.0"));
local->cost_default = atof(LDEF(COST_DEFAULT_STRING, "4.01"));
local->cost_scale = atof(LDEF(COST_SCALE_STRING, "-0.25"));
local->cost_offset = atof(LDEF(COST_OFFSET_STRING, "3.0"));
local->extra_pairs = atoi(LDEF(EXTRA_PAIRS_STRING, "1"));
local->extra_any = atoi(LDEF(EXTRA_ANY_STRING, "1"));
}
else
{
local->extra_pairs = 0;
}
// -----------------------------------------------------
// Pair stuff.
local->pair_disjuncts = atoi(LDEF(PAIR_DISJUNCTS_STRING, "4"));
// Setting pair-disjuncts to zero disables pair prococessing
if (0 < local->pair_disjuncts or 0 < local->extra_pairs)
{
// Costs might be static, precomputed, located at a given key.
// Or they may be dynamic, coming from a formula.
const char* mikp = get_dict_define(dict, PAIR_KEY_STRING);
const char* mifm = get_dict_define(dict, PAIR_FORMULA_STRING);
if (mikp and mifm)
prt_error("Error: Only one of `pair-key` or `pair-formula` allowed!\n");
else if (nullptr == mikp and nullptr == mifm)
prt_error("Error: One of `pair-key` or `pair-formula` must be given!\n");
if (mikp)
{
Handle miki = Sexpr::decode_atom(mikp);
local->mikey = local->asp->add_atom(miki);
}
if (mifm)
{
Handle mifl = Sexpr::decode_atom(mifm);
local->miformula = local->asp->add_atom(mifl);
}
const char* prps = LDEF(PAIR_PREDICATE_STRING, "(BondNode \"ANY\")");
Handle prph = Sexpr::decode_atom(prps);
local->prp = local->asp->add_atom(prph);
local->pair_index = atoi(LDEF(PAIR_INDEX_STRING, "1"));
local->pair_cutoff = atof(LDEF(PAIR_CUTOFF_STRING, "4.0"));
local->pair_default = atof(LDEF(PAIR_DEFAULT_STRING, "4.01"));
local->pair_scale = atof(LDEF(PAIR_SCALE_STRING, "-0.25"));
local->pair_offset = atof(LDEF(PAIR_OFFSET_STRING, "3.0"));
local->pair_with_any = atoi(LDEF(PAIR_WITH_ANY_STRING, "1"));
local->pair_dict = create_pair_cache_dict(dict);
}
local->any_disjuncts = atoi(LDEF(ANY_DISJUNCTS_STRING, "0"));
// The any_default is used to set the costs on UNKNOWN-WORD
// XXX FIXME, maybe there should be a different parameter for this?
local->any_default = atof(LDEF(ANY_DEFAULT_STRING, "2.6"));
dict->as_server = (void*) local;
local->any_expr = make_any_exprs(dict, dict->Exp_pool);
local->enable_unknown_word = atoi(LDEF(ENABLE_UNKNOWN_WORD_STRING, "1"));
if (local->enable_unknown_word)
{
const char* ukw = string_set_add(UNKNOWN_WORD, dict->string_set);
make_dn(dict, local->any_expr, ukw);
}
if (local->using_external_as) return true;
if (nullptr == local->stnp) return true;
// --------------------
// If we are here, then we manage our own private connection
// to storage. We will be managing opening and closing of it,
// and the fetching of data from it.
std::string stone = local->stnp->to_short_string();
const char * stoname = stone.c_str();
#define SHLIB_CTOR_HACK 1
#ifdef SHLIB_CTOR_HACK
/* The cast below forces the shared lib constructor to run. */
/* That's needed to force the factory to get installed. */
/* We need a more elegant solution to this. */
Type snt = local->stnp->get_type();
if (COG_SIMPLE_STORAGE_NODE == snt)
local->stnp = CogSimpleStorageNodeCast(local->stnp);
else if (COG_STORAGE_NODE == snt)
local->stnp = CogStorageNodeCast(local->stnp);
else if (FILE_STORAGE_NODE == snt)
local->stnp = FileStorageNodeCast(local->stnp);
else if (MONO_STORAGE_NODE == snt)
local->stnp = MonoStorageNodeCast(local->stnp);
else if (ROCKS_STORAGE_NODE == snt)
local->stnp = RocksStorageNodeCast(local->stnp);
else
{
prt_error("Error: Unknown storage %s\n", stoname);
return false;
}
#endif
local->stnp->open();
if (local->stnp->connected())
prt_error("Info: Connected to %s\n", stoname);
else
{
prt_error("Error: Failed to connect to %s\n", stoname);
return false;
}
// If a formula is in use, then we need to fetch the
// formula definition (the miformula is just it's name;
// its a DefinedProcedureNode)
if (local->miformula)
{
local->stnp->fetch_incoming_by_type(local->miformula, DEFINE_LINK);
local->stnp->barrier();
}
// Get last issued link ID.
fetch_link_id(local);
return true;
}
/// Close the connection to the StorageNode (e.g. CogServer.)
/// To be used only if the everything has been fetched, and the
/// dict is now in local RAM. The dict remains usable, after
/// closing the connection. Only local StorageNodes are closed.
/// External storage nodes will remain open, but will no longer
/// be used.
void as_storage_close(Dictionary dict)
{
if (nullptr == dict->as_server) return;
Local* local = (Local*) (dict->as_server);
// Record the last unissued id.
store_link_id(local);
if (not local->using_external_as and local->stnp)
local->stnp->close();
local->stnp = nullptr;
}
/// Close the connection to the AtomSpace. This will also empty out
/// the local dictionary, and so the dictionary will no longer be
/// usable after a close.
void as_close(Dictionary dict)
{
logger().info("Atomese: Close dict `%s`", dict->name);
as_storage_close(dict);
if (nullptr == dict->as_server) return;
Local* local = (Local*) (dict->as_server);
if (local->pair_dict)
{
free_dict_node_recursive(local->pair_dict->root);
free(local->pair_dict);
}
delete local;
dict->as_server = nullptr;
// Clear the cache as well
free_dictionary_root(dict);
dict->num_entries = 0;
}
// ===============================================================
/**
* Specialized version of `condesc_setup()`, tuned for Atomese.
* When new expressions are created (even temporary ones, e.g. for MST)
* the connectors in those expressions are placed into a connector
* hash table. The connectors are then compiled into a form that is
* used to speed up matching. For a one-time dictionary load, this is
* done by `condesc_setup()`.
*
* For Atomese, new connectors dribble in with each new sentance.
* As there are millions grand-total, calling `condesc_setup()` is
* stunningly inefficient; only the new ones need to be processed.
* That's what this routine does.
*
* Limitations:
* ++ All connectors are marked UNLIMITED_LEN.
* ++ All connectors are assumed to be upper-case only, with no
* lower-case parts. (That means that there will never be two or
* more connectors with the same upper-case part, thus shortening
* the qsort, below.)
*/
static void update_condesc(Dictionary dict)
{
ConTable *ct = &dict->contable;
// How many new ones are there?
size_t num_new = ct->num_con - ct->last_num;
if (0 == num_new) return;
condesc_t **sdesc = (condesc_t **) malloc(num_new * sizeof(condesc_t *));
size_t i = 0;
for (size_t n = 0; n < ct->size; n++)
{
condesc_t *condesc = ct->hdesc[n].desc;
if (NULL == condesc) continue;
if (UINT32_MAX != condesc->uc_num) continue;
calculate_connector_info(condesc);
condesc->more->length_limit = UNLIMITED_LEN;
sdesc[i++] = condesc;
}
qsort(sdesc, num_new, sizeof(condesc_t *),
condesc_by_uc_constring);
/* Enumerate the connectors according to their UC part. */
int uc_num = ct->num_uc;
sdesc[0]->uc_num = uc_num;
for (size_t n=1; n < num_new; n++)
{
condesc_t **condesc = &sdesc[n];
if (condesc[0]->more->uc_length != condesc[-1]->more->uc_length)
{
/* We know that the UC part has been changed. */
uc_num++;
}
else
{
const char *uc1 = &condesc[0]->more->string[condesc[0]->more->uc_start];
const char *uc2 = &condesc[-1]->more->string[condesc[-1]->more->uc_start];
if (0 != strncmp(uc1, uc2, condesc[0]->more->uc_length))
{
uc_num++;
}
}
//printf("%5d constring=%s\n", uc_num, condesc[0]->string);
condesc[0]->uc_num = uc_num;
}
lgdebug(+11, "Dictionary %s: added %zu different connectors "
"(%d with a different UC part)\n",
dict->name, ct->num_con, uc_num+1);
ct->num_uc = uc_num + 1;
ct->last_num = ct->num_con;
free(sdesc);
}
// ===============================================================
// The sentence that this thread is working with. Assume one thread
// per Sentence, which should be a perfectly valid assumption.
thread_local Sentence sentlo = nullptr;
thread_local HandleSeq sent_words;
/// Make a note of all of the words in the sentence. We need this,
/// to pre-prune MST word-pairs, as well as disjuncts decorated with
/// extra pairs.
///
/// XXX A future enhancement would be to pre-prune disjuncts as well,
/// although it is not obvious that we could do any better here, than
/// the generic expression pruning code could do.
void as_start_lookup(Dictionary dict, Sentence sent)
{
Local* local = (Local*) (dict->as_server);
sentlo = sent;
lgdebug(D_USER_INFO, "Atomese: Start dictionary lookup for >>%s<<\n",
sent->orig_sentence);
if (0 < local->pair_disjuncts or 0 < local->extra_pairs)
{
for(size_t i=0; i<sent->length; i++)
{
const char* wstr = sent->word[i].unsplit_word;
if (wstr)
{
if (0 == i && 0 == strcmp(wstr, LEFT_WALL_WORD))
wstr = "###LEFT-WALL###";
Handle wrd = local->asp->get_node(WORD_NODE, wstr);
if (wrd)
sent_words.push_back(wrd);
}
int j = 0;
const char* astr = sent->word[i].alternatives[j];
while (astr)
{
Handle wrd = local->asp->get_node(WORD_NODE, astr);
if (wrd)
sent_words.push_back(wrd);
j++;
astr = sent->word[i].alternatives[j];
}
}
}
}
void as_end_lookup(Dictionary dict, Sentence sent)
{
Local* local = (Local*) (dict->as_server);
sentlo = nullptr;
if (0 < local->pair_disjuncts or 0 < local->extra_pairs)
sent_words.clear();
lgdebug(D_USER_INFO, "Atomese: Finish dictionary lookup for >>%s<<\n",
sent->orig_sentence);
// Create connector descriptors for any new connectors.
std::lock_guard<std::mutex> guard(local->dict_mutex);
update_condesc(dict);
}
/// Thread-safe dict lookup.
static bool locked_dict_node_exists_lookup(Dictionary dict, const char *s)
{
Local* local = (Local*) (dict->as_server);
std::lock_guard<std::mutex> guard(local->dict_mutex);
return dict_node_exists_lookup(dict, s);
}
/// Return true if the given word can be found in the dictionary,
/// else return false.
bool as_boolean_lookup(Dictionary dict, const char *s)
{
if (locked_dict_node_exists_lookup(dict, s))
{
lgdebug(D_USER_INFO, "Atomese: Found in local dict: >>%s<<\n", s);
return true;
}
Local* local = (Local*) (dict->as_server);
if (local->enable_unknown_word and 0 == strcmp(s, "<UNKNOWN-WORD>"))
return true;
if (0 == strcmp(s, LEFT_WALL_WORD))
s = "###LEFT-WALL###";
if (local->enable_sections and section_boolean_lookup(dict, s))
{
if (0 < local->extra_pairs or 0 < local->pair_disjuncts)
pair_boolean_lookup(dict, s);
return true;
}
if ((0 < local->pair_disjuncts or 0 < local->extra_pairs) and
pair_boolean_lookup(dict, s))
{
return true;
}
return false;
}
// ===============================================================
/// Add `item` to the linked list `orhead`, using a OR operator.
void or_enchain(Pool_desc* pool, Exp* &orhead, Exp* item)
{
if (nullptr == item) return; // no-op
if (nullptr == orhead)
{
orhead = item;
return;
}
// Unary OR-nodes are not allowed; they will cause assorted
// algos to croak. So the first OR node must have two.
if (OR_type != orhead->type)
{
orhead = make_or_node(pool, item, orhead);
return;
}
/* Link new connectors to the head */
item->operand_next = orhead->operand_first;
orhead->operand_first = item;
}
/// Add `item` to the left end of the linked list `andhead`, using
/// an AND operator. The `andtail` is used to track the right side
/// of the list.
void and_enchain_left(Pool_desc* pool, Exp* &andhead, Exp* &andtail, Exp* item)
{
if (nullptr == item) return; // no-op
if (nullptr == andhead)
{
andhead = make_and_node(pool, item, NULL);
return;
}
/* Link new connectors to the head */
item->operand_next = andhead->operand_first;
andhead->operand_first = item;
if (nullptr == andtail)
andtail = item->operand_next;
}
/// Add `item` to the right end of the linked list `andhead`, using
/// an AND operator. The `andtail` is used to track the right side
/// of the list.
void and_enchain_right(Pool_desc* pool, Exp* &andhead, Exp* &andtail, Exp* item)
{
if (nullptr == item) return; // no-op
if (nullptr == andhead)
{
andhead = make_and_node(pool, item, NULL);
return;
}
/* Link new connectors to the tail */
if (nullptr == andtail)
andtail = andhead->operand_first;
andtail->operand_next = item;
andtail = item;
}
// Report Number of entries in the dict, and also RAM usage.
static void report_dict_usage(Dictionary dict)
{
// We could also print pool_num_elements_issued() but this is not
// interesting; it's slightly less than pool_size().
logger().info("LG Dict: %lu entries; %lu Exp_pool elts; %lu MiBytes",
dict->num_entries,
pool_size(dict->Exp_pool),
pool_bytes(dict->Exp_pool) / (1024 * 1024));
}
/// Given an expression, wrap it with a Dict_node and insert it into
/// the dictionary.
void make_dn(Dictionary dict, Exp* exp, const char* ssc)
{
Dict_node* dn = dict_node_new();
dn->string = ssc;
dn->exp = exp;
// Cache the result; avoid repeated lookups.
dict->root = dict_node_insert(dict, dict->root, dn);
dict->num_entries++;
// Rebalance the tree every now and then.
if (0 == dict->num_entries%60)
{
dict->root = dsw_tree_to_vine(dict->root);
report_dict_usage(dict);
dict->root = dsw_vine_to_tree(dict->root, dict->num_entries);
}
}
const char* ss_add(const char *s, Dictionary dict)
{
Local* local = (Local*) (dict->as_server);
std::lock_guard<std::mutex> guard(local->dict_mutex);
return string_set_add(s, dict->string_set);
}
/// Given a word, return the collection of Dict_nodes holding the
/// expressions for that word.
Dict_node * as_lookup_list(Dictionary dict, const char *s)
{
// The tokenizer might call us, asking about COMMON_ENTITY_MARKER
// in order to figure out if a word can be down-cased. This will
// happen outside the sentence-begin-end expression context, so
// sentlo will be null. Just ignore this; we don't have entity
// markers. Anyway, handling of this kind of stuff needs to be
// done differently, anyway. Meanwhile, avoid null ptr derefernce.
if (nullptr == sentlo) return nullptr;
Local* local = (Local*) (dict->as_server);
if (0 == strcmp(s, LEFT_WALL_WORD)) s = "###LEFT-WALL###";
const char* ssc = ss_add(s, dict);
Handle wrd = local->asp->get_node(WORD_NODE, ssc);
if (nullptr == wrd)
{
if (local->enable_unknown_word)
return dict_node_lookup(dict, UNKNOWN_WORD);
return nullptr;
}
// Create expressions consisting entirely of word-pair links.
// These are "temporary", and always go into Sentence::Exp_pool.
Exp* cpr = nullptr;
if (0 < local->pair_disjuncts)
cpr = make_cart_pairs(dict, wrd, sentlo->Exp_pool, sent_words,
local->pair_disjuncts,
local->pair_with_any);
// If sections are enabled, get those. Or-chain them to pairs.
if (local->enable_sections)
{
Dict_node* dn = lookup_section(dict, wrd);
if (nullptr == cpr) return dn;
// Splice in pairs.
if (dn)
{
dn->exp = make_or_node(sentlo->Exp_pool, dn->exp, cpr);
return dn;
}
}
// If we are here, then there are only pairs, no sections.
if (cpr)
{
Dict_node * dn = dict_node_new();
dn->string = ssc;
dn->exp = cpr;
return dn;
}
// Create disjuncts consisting entirely of "ANY" links.
if (local->any_disjuncts)
{
std::lock_guard<std::mutex> guard(local->dict_mutex);
// If it's cached, just return that.
Dict_node* dn = dict_node_lookup(dict, ssc);
if (dn) return dn;
// Make a new one.
make_dn(dict, local->any_expr, ssc);
return dict_node_lookup(dict, ssc);
}
OC_ASSERT(0, "Internal Error! Must have sections, pair or any!");
return nullptr;
}
// This is supposed to provide a wild-card lookup. However,
// There is currently no way to support a wild-card lookup in the
// atomspace: there is no way to ask for all WordNodes that match
// a given regex. There's no regex predicate... this can be hacked
// around in various elegant and inelegant ways, e.g. adding a
// regex predicate to the AtomSpace. Punt for now. This is used
// only for the !! command in the parser command-line tool.
// XXX FIXME. But low priority.
Dict_node * as_lookup_wild(Dictionary dict, const char *s)
{
Dict_node * dn = dict_node_wild_lookup(dict, s);
if (dn) return dn;
as_lookup_list(dict, s);
return dict_node_wild_lookup(dict, s);
}
// Zap all the Dict_nodes that we've added earlier.
// This clears out everything hanging on dict->root
// as well as the expression pool.
// And also the local AtomSpace.
//
// This can be called by link-parser command line, with the !clear
// "special" command.
void as_clear_cache(Dictionary dict)
{
Local* local = (Local*) (dict->as_server);
printf("Prior to clear, dict has %d entries, Atomspace has %lu Atoms\n",
dict->num_entries, local->asp->get_size());
// Free the dict nodes. Free the pair-cache, too.
// Reuse the existing Exp pool.
free_dict_node_recursive(dict->root);
free_dict_node_recursive(local->pair_dict->root);
pool_reuse(dict->Exp_pool);
local->have_pword.clear();
// Clear the local AtomSpace too.
// Easiest way to do this is to just close and reopen
// the connection.
AtomSpacePtr savea = external_atomspace;
StorageNodePtr saves = external_storage;
if (local->using_external_as)
{
external_atomspace = local->asp;
external_storage = local->stnp;
}
as_close(dict);
as_open(dict);
external_atomspace = savea;
external_storage = saves;
as_boolean_lookup(dict, LEFT_WALL_WORD);
}
#endif /* HAVE_ATOMESE */