/*
* Term.java
*
* Copyright (C) 2008 Pei Wang
*
* This file is part of Open-NARS.
*
* Open-NARS is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Open-NARS 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 General Public License
* along with Open-NARS. If not, see <http://www.gnu.org/licenses/>.
*/
package nars.language;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import nars.storage.Memory;
import nars.config.Parameters;
import nars.inference.TemporalRules;
import nars.io.Symbols;
import nars.io.Symbols.NativeOperator;
import nars.io.Texts;
import nars.operator.Operation;
import nars.operator.Operator;
//import nars.util.sort.SortedList;
/**
* Term is the basic component of Narsese, and the object of processing in NARS.
* <p>
* A Term may have an associated Concept containing relations with other Terms.
* It is not linked in the Term, because a Concept may be forgot while the Term
* exists. Multiple objects may represent the same Term.
*/
public class Term implements AbstractTerm {
private static final Map<CharSequence,Term> atoms = new HashMap();
final public static Term SELF = Term.get("SELF");
final public static boolean isSelf(final Term t) {
return SELF.equals(t);
}
public NativeOperator operator() {
return NativeOperator.ATOM;
}
public boolean isHigherOrderStatement() { //==> <=>
return (this instanceof Equivalence) || (this instanceof Implication);
}
public boolean isExecutable(Memory mem) {
//don't allow ^want and ^believe to be active/have an effect,
//which means its only used as monitor
boolean isOp=this instanceof Operation;
if(isOp) {
Operator op=((Operation)this).getOperator(); //the following part may be refactored after we
//know more about how the NAL9 concepts should really interact together:
/*if(op.equals(mem.getOperator("^want")) || op.equals(mem.getOperator("^believe"))) {
return false;
}*/
}
return isOp;
}
public interface TermVisitor {
public void visit(Term t, Term superterm);
}
protected CharSequence name = null;
/**
* Default constructor that build an internal Term
*/
protected Term() {
}
/**
* Constructor with a given name
*
* @param name A String as the name of the Term
*/
public Term(final CharSequence name) {
setName(name);
}
/** gets the atomic term given a name */
public final static Term get(final CharSequence name) {
Term x = atoms.get(name);
if (x != null) return x;
x = new Term(name);
atoms.put(name, x);
return x;
}
/** gets the atomic term of an integer */
public final static Term get(final int i) {
//fast lookup for single digits
switch (i) {
case 0: return get("0");
case 1: return get("1");
case 2: return get("2");
case 3: return get("3");
case 4: return get("4");
case 5: return get("5");
case 6: return get("6");
case 7: return get("7");
case 8: return get("8");
case 9: return get("9");
}
return get(Integer.toString(i));
}
/**
* Reporting the name of the current Term.
*
* @return The name of the term as a String
*/
@Override
public CharSequence name() {
return name;
}
/**
* Make a new Term with the same name.
*
* @return The new Term
*/
@Override
public Term clone() {
//avoids setName and its intern(); the string will already be intern:
Term t = new Term();
t.name = name();
return t;
}
public Term cloneDeep() {
return clone();
}
/**
* Equal terms have identical name, though not necessarily the same
* reference.
*
* @return Whether the two Terms are equal
* @param that The Term to be compared with the current Term
*/
@Override
public boolean equals(final Object that) {
if (that == this) return true;
if (getClass() != this.getClass()) return false; //optimization, if complexity is different they cant be equal
return this.getComplexity() == ((Term) that).getComplexity() && name().equals(((Term)that).name());
}
/**
* Produce a hash code for the term
*
* @return An integer hash code
*/
@Override
public int hashCode() {
return name().hashCode();
}
/**
* Check whether the current Term can name a Concept.
* isConstant means if the term contains free variable
* True if:
* has zero variables, or
* uses several instances of the same variable
* False if it uses one instance of a variable ("free" like a "free radical" in chemistry).
* Therefore it may be considered Constant, yet actually contain variables.
*
* @return A Term is constant by default
*/
@Override
public boolean isConstant() {
return true;
}
public int getTemporalOrder() {
return TemporalRules.ORDER_NONE;
}
public void recurseTerms(final TermVisitor v, Term parent) {
v.visit(this, parent);
if (this instanceof CompoundTerm) {
for (Term t : ((CompoundTerm)this).term) {
t.recurseTerms(v, this);
}
}
}
public void recurseSubtermsContainingVariables(final TermVisitor v) {
recurseTerms(v, null);
}
public void recurseSubtermsContainingVariables(final TermVisitor v, Term parent) {
if (!hasVar()) return;
v.visit(this, parent);
if (this instanceof CompoundTerm) {
for (Term t : ((CompoundTerm)this).term) {
t.recurseSubtermsContainingVariables(v, this);
}
}
}
/**
* The syntactic complexity, for constant atomic Term, is 1.
*
* @return The complexity of the term, an integer
*/
public short getComplexity() {
return 1;
}
/** only method that should modify Term.name. also caches hashcode
* @return whether the name was changed
*/
protected void setName(final CharSequence newName) {
this.name = newName;
// if (this.name!=null) {
// if (this.name.equals(newName)) {
// //name is the same
// return false;
// }
// }
//
// if (newName == null)
// return this.name != null;
//
// if ((newName.getClass() == String.class) && (newName.length() <= Parameters.INTERNED_TERM_NAME_MAXLEN)) {
//
// this.name = ((String)newName).intern();
// }
// else {
// this.name = newName;
// }
// return true;
}
/**
* @param that The Term to be compared with the current Term
* @return The same as compareTo as defined on Strings
*/
@Override
public int compareTo(final AbstractTerm that) {
if (that==this) return 0;
if (Parameters.TERM_ELEMENT_EQUIVALENCY) {
if (!getClass().equals(that.getClass())) {
//differnt class, use class as ordering
return getClass().getSimpleName().compareTo(that.getClass().getSimpleName());
}
else {
//same class, compare by name()
return Texts.compareTo(name(), that.name());
}
}
else {
//previously: Orders among terms: variable < atomic < compound
if ((that instanceof Variable) && (getClass()!=Variable.class))
return 1;
else if ((this instanceof Variable) && (that.getClass()!=Variable.class))
return -1;
return Texts.compareTo(name(), that.name());
}
}
public int containedTemporalRelations() {
return 0;
}
/**
* Recursively check if a compound contains a term
*
* @param target The term to be searched
* @return Whether the two have the same content
*/
public boolean containsTermRecursively(final Term target) {
if(target==null) {
return false;
}
return equals(target);
}
/** whether this contains a term in its components. */
public boolean containsTerm(final Term target) {
return equals(target);
}
/**
* The same as getName by default, used in display only.
*
* @return The name of the term as a String
*/
@Override
public final String toString() {
return name().toString();
}
/** Creates a quote-escaped term from a string. Useful for an atomic term that is meant to contain a message as its name */
public static Term text(String t) {
return Term.get(Texts.escape('"' + t + '"').toString());
}
/**
* Whether this compound term contains any variable term
*
* @return Whether the name contains a variable
*/
@Override public boolean hasVar() {
return false;
}
public boolean hasVar(final char type) {
switch (type) {
case Symbols.VAR_DEPENDENT: return hasVarDep();
case Symbols.VAR_INDEPENDENT: return hasVarIndep();
case Symbols.VAR_QUERY: return hasVarQuery();
}
throw new RuntimeException("Invalid variable type: " + type);
}
public boolean hasVarIndep() {
return false;
}
public boolean hasVarDep() {
return false;
}
public boolean hasVarQuery() {
return false;
}
public static TreeSet<Term> toSortedSet(final Term... arg) {
//use toSortedSetArray where possible
TreeSet<Term> t = new TreeSet();
for (Term x : arg) t.add(x);
return t;
}
public final static Term[] EmptyTermArray = new Term[0];
public static Term[] toSortedSetArray(final Term... arg) {
switch (arg.length) {
case 0: return EmptyTermArray;
case 1: return new Term[] { arg[0] };
case 2:
Term a = arg[0];
Term b = arg[1];
int c = a.compareTo(b);
if (Parameters.DEBUG) {
//verify consistency of compareTo() and equals()
boolean equal = a.equals(b);
if ((equal && (c!=0)) || (!equal && (c==0))) {
throw new RuntimeException("invalid order: " + a + " = " + b);
}
}
if (c < 0) return new Term[] { a, b };
else if (c > 0) return new Term[] { b, a };
else if (c == 0) return new Term[] { a }; //equal
}
//TODO fast sorted array for arg.length == 3
//terms > 2:
TreeSet<Term> s = new TreeSet();
//SortedList<Term> s = new SortedList(arg.length);
//s.setAllowDuplicate(false);
for (Term a: arg) s.add(a);
return s.toArray(new Term[s.size()] );
/*
TreeSet<Term> s = toSortedSet(arg);
//toArray didnt seem to work, but it might. in the meantime:
Term[] n = new Term[s.size()];
int j = 0;
for (Term x : s) {
n[j++] = x;
}
return n;
*/
}
/** performs a thorough check of the validity of a term (by cloneDeep it) to see if it's valid */
public static boolean valid(Term content) {
try {
Term cloned = content.cloneDeep();
return cloned!=null;
}
catch (Throwable e) {
/*if (Parameters.DEBUG && Parameters.DEBUG_INVALID_SENTENCES) {
System.err.println("INVALID TERM: " + content);
e.printStackTrace();
}*/
return false;
}
}
public boolean subjectOrPredicateIsIndependentVar() {
if(this instanceof Statement) {
Statement cont=(Statement)this;
if(cont.getSubject() instanceof Variable) {
Variable v=(Variable) cont.getSubject();
if(v.hasVarIndep()) {
return true;
}
}
if(cont.getPredicate()instanceof Variable) {
Variable v=(Variable) cont.getPredicate();
if(v.hasVarIndep()) {
return true;
}
}
}
return false;
}
}