OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace-storage / opencog / persist / prolog / DecodeProlog.cc

211 lines · cc ref:
/*
 * DecodeProlog.cc
 *
 * Copyright (C) 2022 Linas Vepstas
 *
 * Author: Linas Vepstas <linasvepstas@gmail.com>  September 2022
 *
 * 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/base/Link.h>
#include <opencog/atoms/base/Node.h>

#include "Prolog.h"

using namespace opencog;

/// Parse one or more clauses, e.g.
/// foo(X) :- bar(X). bing(bang,bong). food(pizza).
HandleSeq Prolog::parse(const std::string& sexpr, size_t& l, size_t& r)
{
	HandleSeq clauses;
	while (std::string::npos != l)
	{
		Handle h(get_next_expr(sexpr, l, r));
		clauses.emplace_back(h);

		if (std::string::npos == l)
			throw SyntaxException(TRACE_INFO, "Expecting period at end.");

		l++;
		l = sexpr.find_first_not_of(" \t\n", l);
	}

	return clauses;
}

// ---------------------------------------------------------------

// Lower-case tokens, or quoted tokens, are concept nodes.
// Upper-case tokens are Variables.
static Handle make_tok(const std::string& tok)
{
	char c = tok[0];

	// Variables begin with an underscore, or uppercase letter.
	if ('_' == c or isupper(c))
		return HandleCast(createNode(VARIABLE_NODE, tok));

	return HandleCast(createNode(CONCEPT_NODE, tok));
}

// Parse factual assertions such as
// likes(john, mary) or food(pizza)
// but also fragments of clauses, such as
// likes(X,Y)
static Handle get_fact(const std::string& sexpr, size_t& l, size_t &r)
{
	l = sexpr.find_first_not_of(" \t\n", l);
	if (std::string::npos == l)
		throw SyntaxException(TRACE_INFO, "Unexpected blank line");

	r = sexpr.find_first_of("( \t\n", l+1);
	const std::string& spred = sexpr.substr(l, r-l);
	Handle pred = createNode(PREDICATE_NODE, std::move(spred));

	l = sexpr.find_first_not_of(" \t\n", r);
	if (std::string::npos == l or '(' != sexpr[l])
		throw SyntaxException(TRACE_INFO, "Expecting open-paren");

	l++; // step past open paren

	HandleSeq clist;
	while (std::string::npos != l)
	{
		// Matching quotes, if quoted.
		if ('\'' == sexpr[l])
		{
			r = sexpr.find('\'', l+1);
			if (std::string::npos != r) r++;
			else
				throw SyntaxException(TRACE_INFO, "Unbalanced quotes");
		}
		else
			r = sexpr.find_first_of(",) \t\n", l);
		const std::string& litrl = sexpr.substr(l, r-l);

		if ('\'' == litrl[0] or std::string::npos == litrl.find('('))
			clist.emplace_back(make_tok(litrl));
		else
		{
			// The literal is yet another fact. Yuck.
			// Go and nest them. (For now? Maybe fix later?)
			clist.emplace_back(get_fact(sexpr, l, r));
			r = l;
		}

		if (')' == sexpr[r]) break;
		l = sexpr.find_first_not_of(", \t\n", r);
	}
	if (std::string::npos == l)
		throw SyntaxException(TRACE_INFO, "Expecting close-paren");

	l = sexpr.find_first_not_of(" \t\n", r+1);
	r = std::string::npos;

	Handle evl = createLink(EVALUATION_LINK,
		pred, createLink(std::move(clist), LIST_LINK));
	return evl;
}

// ---------------------------------------------------------------
//
// Stub for handling queries.
// There are several basic types:
// 1) ?- likes(alice,john).
//    Return true of false; tests for presence. (SatisfactionLink) Awkward!
// 2) ?- likes(alice, _).
//    Return true of false;, alice likes someone, but its indeterminate.
// 3) ?- likes(alice, Who).
//    Return grounding of Who. Standard GetLink.
//    Caution: this may require chaining to solve. Ugh.
// 4) Unification, chaining, ... etc.

static Handle formulate_query(const std::string& sexpr, size_t& l, size_t &r)
{
#if 0
	// Skip past the ?- at the start.
	l = sexpr.find_first_not_of(" \t\n", l+2);
	if (std::string::npos == l)
		throw SyntaxException(TRACE_INFO, "Unexpected blank line");

	// Lets see if we can come up with one of the simple cases.
	// Looking for case 3.
	Handle fac = get_fact(sexpr, l, r);
#endif

	throw SyntaxException(TRACE_INFO, "Queries are not (yet) supported!");
}

// ---------------------------------------------------------------
// Parse clauses such as
// likes(john, mary).
// or
// child(X,Y) :- parent(Y,X).
// Clauses must be terminated by a period.
Handle Prolog::get_next_expr(const std::string& sexpr, size_t& l, size_t &r)
{
	l = sexpr.find_first_not_of(" \t\n", l);
	if (std::string::npos == l)
		throw SyntaxException(TRACE_INFO, "Unexpected blank line");

	// Maybe it's a query?
	if ('?' == sexpr[l])
		return formulate_query(sexpr, l, r);

	// Not a query. Get first part of clause.
	Handle fac = get_fact(sexpr, l, r);

	if (std::string::npos == l)
		throw SyntaxException(TRACE_INFO, "Expecting terminating period");

	// Just a single fact.
	if ('.' == sexpr[l])
		return fac;

	// Is it a Horn clause?
	if (sexpr.substr(l, 2) != ":-")
		throw SyntaxException(TRACE_INFO, "Badly formed expression");

	l += 2;

	// Loop over conjunctions.
	HandleSeq premis;
	while (std::string::npos != l)
	{
		Handle fac = get_fact(sexpr, l, r);
		premis.emplace_back(fac);
		if (std::string::npos == l)
			throw SyntaxException(TRACE_INFO, "Badly formed expression");
		if ('.' == sexpr[l]) break;
		if (',' != sexpr[l])
			throw SyntaxException(TRACE_INFO, "Badly formed expression");
		l++;
	}

	// Not a conjunction, just a single term.
	if (1 == premis.size())
		return HandleCast(createLink(IMPLICATION_LINK, premis[0], fac));

	// Conjunction of multiple terms.
	Handle imp = createLink(IMPLICATION_LINK,
		createLink(std::move(premis), AND_LINK), fac);
	return imp;
}

/* ===================== END OF FILE ===================== */