package nars.io; import static java.lang.Float.parseFloat; import java.util.ArrayList; import nars.storage.Memory; import nars.NAR; import nars.config.Parameters; import nars.entity.BudgetValue; import nars.entity.Sentence; import nars.entity.Stamp; import nars.entity.Task; import nars.entity.TruthValue; import static nars.inference.BudgetFunctions.truthToQuality; import nars.io.Symbols; import static nars.io.Symbols.ARGUMENT_SEPARATOR; import static nars.io.Symbols.BUDGET_VALUE_MARK; import static nars.io.Symbols.GOAL_MARK; import static nars.io.Symbols.INPUT_LINE_PREFIX; import static nars.io.Symbols.JUDGMENT_MARK; import nars.io.Symbols.NativeOperator; import static nars.io.Symbols.NativeOperator.COMPOUND_TERM_CLOSER; import static nars.io.Symbols.NativeOperator.SET_EXT_CLOSER; import static nars.io.Symbols.NativeOperator.SET_INT_CLOSER; import static nars.io.Symbols.NativeOperator.STATEMENT_CLOSER; import static nars.io.Symbols.OUTPUT_LINE_PREFIX; import static nars.io.Symbols.PREFIX_MARK; import static nars.io.Symbols.QUESTION_MARK; import static nars.io.Symbols.QUEST_MARK; import static nars.io.Symbols.STAMP_CLOSER; import static nars.io.Symbols.STAMP_OPENER; import static nars.io.Symbols.TRUTH_VALUE_MARK; import static nars.io.Symbols.VALUE_SEPARATOR; import static nars.io.Symbols.getCloser; import static nars.io.Symbols.getOpener; import static nars.io.Symbols.getRelation; import static nars.io.Symbols.isRelation; import nars.language.Interval; import nars.language.SetExt; import nars.language.SetInt; import nars.language.Statement; import nars.language.Tense; import nars.language.Term; import nars.language.Terms; import nars.language.Variable; import nars.operator.Operation; import static nars.operator.Operation.make; import nars.operator.Operator; import static java.lang.String.valueOf; import static nars.io.Symbols.getOperator; import static nars.language.Variables.containVar; import static nars.language.Statement.make; import static nars.language.Statement.make; import static java.lang.String.valueOf; import static nars.io.Symbols.getOperator; import static nars.language.Variables.containVar; import static nars.language.Statement.make; /** * Utility methods for working and reacting to Narsese input. * This will eventually be integrated with NarseseParser for systematic * parsing and prediction of input. */ public class Narsese { public final Memory memory; /** * All kinds of invalid addInput lines */ public static class InvalidInputException extends Exception { /** * An invalid addInput line. * @param s type of error */ InvalidInputException(String s) { super(s); } } public Narsese(Memory memory) { this.memory = memory; } public Narsese(NAR n) { this(n.memory); } /** * Parse a line of addInput experience * <p> * called from ExperienceIO.loadLine * * @param buffer The line to be parsed * @param memory Reference to the memory * @param time The current time * @return An experienced task */ public Task parseNarsese(StringBuilder buffer) throws InvalidInputException { int i = buffer.indexOf(valueOf(PREFIX_MARK)); if (i > 0) { String prefix = buffer.substring(0, i).trim(); if (prefix.equals(INPUT_LINE_PREFIX)) { buffer.delete(0, i + 1); } else if (prefix.equals(OUTPUT_LINE_PREFIX)) { //ignore outputs return null; } } char c = buffer.charAt(buffer.length() - 1); if (c == STAMP_CLOSER) { //ignore stamp int j = buffer.lastIndexOf(valueOf(STAMP_OPENER)); buffer.delete(j - 1, buffer.length()); } c = buffer.charAt(buffer.length() - 1); if (c == ']') { int j = buffer.lastIndexOf(valueOf('[')); buffer.delete(j-1, buffer.length()); } return parseTask(buffer.toString().trim()); } // public static Sentence parseOutput(String s) { // Term content = null; // char punc = 0; // TruthValue truth = null; // // try { // StringBuilder buffer = new StringBuilder(s); // //String budgetString = getBudgetString(buffer); // String truthString = getTruthString(buffer); // String str = buffer.toString().trim(); // int last = str.length() - 1; // punc = str.charAt(last); // //Stamp stamp = new Stamp(time); // truth = parseTruth(truthString, punc); // // // /*Term content = parseTerm(str.substring(0, last)); // if (content == null) throw new InvalidInputException("Content term missing");*/ // } // catch (InvalidInputException e) { // System.err.println("TextInput.parseOutput: " + s + " : " + e.toString()); // } // return new Sentence(content, punc, truth, null); // } /** * Enter a new Task in String into the memory, called from InputWindow or * locally. * * @param s the single-line addInput String * @param memory Reference to the memory * @param time The current time * @return An experienced task */ public Task parseTask(String s) throws InvalidInputException { StringBuilder buffer = new StringBuilder(Texts.escape(s)); String budgetString = getBudgetString(buffer); String truthString = getTruthString(buffer); Tense tense = parseTense(buffer); String str = buffer.toString().trim(); int last = str.length() - 1; char punc = str.charAt(last); Stamp stamp = new Stamp(-1 /* if -1, will be set right before the Task is input */, tense, memory.newStampSerial(), memory.param.duration.get()); TruthValue truth = parseTruth(truthString, punc); Term content = parseTerm(str.substring(0, last)); if (content == null) throw new InvalidInputException("Content term missing"); Sentence sentence = new Sentence(content, punc, truth, stamp); //if ((content instanceof Conjunction) && Variable.containVarDep(content.getName())) { // sentence.setRevisible(false); //} BudgetValue budget = parseBudget(budgetString, punc, truth); Task task = new Task(sentence, budget); return task; } /* ---------- react values ---------- */ /** * Return the prefix of a task symbol that contains a BudgetValue * * @param s the addInput in a StringBuilder * @return a String containing a BudgetValue * @throws nars.io.StringParser.InvalidInputException if the addInput cannot be parsed into a BudgetValue */ private static String getBudgetString(StringBuilder s) throws InvalidInputException { if (s.charAt(0) != BUDGET_VALUE_MARK) { return null; } int i = s.indexOf(valueOf(BUDGET_VALUE_MARK), 1); // looking for the end if (i < 0) { throw new InvalidInputException("missing budget closer"); } String budgetString = s.substring(1, i).trim(); if (budgetString.length() == 0) { throw new InvalidInputException("empty budget"); } s.delete(0, i + 1); return budgetString; } /** * Return the postfix of a task symbol that contains a TruthValue * * @return a String containing a TruthValue * @param s the addInput in a StringBuilder * @throws nars.io.StringParser.InvalidInputException if the addInput cannot be parsed into a TruthValue */ private static String getTruthString(final StringBuilder s) throws InvalidInputException { final int last = s.length() - 1; if (s.charAt(last) != TRUTH_VALUE_MARK) { // use default return null; } final int first = s.indexOf(valueOf(TRUTH_VALUE_MARK)); // looking for the beginning if (first == last) { // no matching closer throw new InvalidInputException("missing truth mark"); } final String truthString = s.substring(first + 1, last).trim(); if (truthString.length() == 0) { // empty usage throw new InvalidInputException("empty truth"); } s.delete(first, last + 1); // remaining addInput to be processed outside s.trimToSize(); return truthString; } /** * react the addInput String into a TruthValue (or DesireValue) * * @param s addInput String * @param type Task type * @return the addInput TruthValue */ private static TruthValue parseTruth(String s, char type) { if ((type == QUESTION_MARK) || (type == QUEST_MARK)) { return null; } float frequency = 1.0f; float confidence = Parameters.DEFAULT_JUDGMENT_CONFIDENCE; if(type==GOAL_MARK) { confidence = Parameters.DEFAULT_GOAL_CONFIDENCE; } if (s != null) { int i = s.indexOf(VALUE_SEPARATOR); if (i < 0) { frequency = parseFloat(s); } else { frequency = parseFloat(s.substring(0, i)); confidence = parseFloat(s.substring(i + 1)); } } return new TruthValue(frequency, confidence); } /** * react the addInput String into a BudgetValue * * @param truth the TruthValue of the task * @param s addInput String * @param punctuation Task punctuation * @return the addInput BudgetValue * @throws nars.io.StringParser.InvalidInputException If the String cannot * be parsed into a BudgetValue */ private static BudgetValue parseBudget(String s, char punctuation, TruthValue truth) throws InvalidInputException { float priority, durability; switch (punctuation) { case JUDGMENT_MARK: priority = Parameters.DEFAULT_JUDGMENT_PRIORITY; durability = Parameters.DEFAULT_JUDGMENT_DURABILITY; break; case QUESTION_MARK: priority = Parameters.DEFAULT_QUESTION_PRIORITY; durability = Parameters.DEFAULT_QUESTION_DURABILITY; break; case GOAL_MARK: priority = Parameters.DEFAULT_GOAL_PRIORITY; durability = Parameters.DEFAULT_GOAL_DURABILITY; break; case QUEST_MARK: priority = Parameters.DEFAULT_QUEST_PRIORITY; durability = Parameters.DEFAULT_QUEST_DURABILITY; break; default: throw new InvalidInputException("unknown punctuation: '" + punctuation + "'"); } if (s != null) { // overrite default int i = s.indexOf(VALUE_SEPARATOR); if (i < 0) { // default durability priority = parseFloat(s); } else { int i2 = s.indexOf(VALUE_SEPARATOR, i+1); if (i2 == -1) i2 = s.length(); priority = parseFloat(s.substring(0, i)); durability = parseFloat(s.substring(i + 1, i2)); } } float quality = (truth == null) ? 1 : truthToQuality(truth); return new BudgetValue(priority, durability, quality); } /** * Recognize the tense of an addInput sentence * @param s the addInput in a StringBuilder * @return a tense value */ public static Tense parseTense(StringBuilder s) { int i = s.indexOf(Symbols.TENSE_MARK); String t = ""; if (i > 0) { t = s.substring(i).trim(); s.delete(i, s.length()); } return Tense.tense(t); } /* ---------- react String into term ---------- */ /** * Top-level method that react a Term in general, which may recursively call itself. * <p> There are 5 valid cases: 1. (Op, A1, ..., An) is a CompoundTerm if Op is a built-in getOperator 2. {A1, ..., An} is an SetExt; 3. [A1, ..., An] is an SetInt; 4. <T1 Re T2> is a Statement (including higher-order Statement); * 5. otherwise it is a simple term. * * @param s0 the String to be parsed * @param memory Reference to the memory * @return the Term generated from the String */ public Term parseTerm(String s) throws InvalidInputException { s = s.trim(); if (s.length() == 0) return null; int index = s.length() - 1; char first = s.charAt(0); char last = s.charAt(index); NativeOperator opener = getOpener(first); if (opener!=null) { switch (opener) { case COMPOUND_TERM_OPENER: if (last == COMPOUND_TERM_CLOSER.ch) { return parseCompoundTerm(s.substring(1, index)); } else { throw new InvalidInputException("missing CompoundTerm closer"); } case SET_EXT_OPENER: if (last == SET_EXT_CLOSER.ch) { return SetExt.make(parseArguments(s.substring(1, index) + ARGUMENT_SEPARATOR)); } else { throw new InvalidInputException("missing ExtensionSet closer"); } case SET_INT_OPENER: if (last == SET_INT_CLOSER.ch) { return SetInt.make(parseArguments(s.substring(1, index) + ARGUMENT_SEPARATOR)); } else { throw new InvalidInputException("missing IntensionSet closer"); } case STATEMENT_OPENER: if (last == STATEMENT_CLOSER.ch) { return parseStatement(s.substring(1, index)); } else { throw new InvalidInputException("missing Statement closer"); } } } else if (Parameters.FUNCTIONAL_OPERATIONAL_FORMAT) { //parse functional operation: // function() // function(a) // function(a,b) //test for existence of matching parentheses at beginning at index!=0 int pOpen = s.indexOf('('); int pClose = s.lastIndexOf(')'); if ((pOpen!=-1) && (pClose!=-1) && (pClose==s.length()-1)) { String operatorString = Operator.addPrefixIfMissing( s.substring(0, pOpen) ); Operator operator = memory.getOperator(operatorString); if (operator == null) { //??? throw new InvalidInputException("Unknown operator: " + operatorString); } String argString = s.substring(pOpen+1, pClose+1); Term[] a; if (argString.length() > 1) { ArrayList<Term> args = parseArguments(argString); a = args.toArray(new Term[args.size()]); } else { //void "()" arguments, default to (SELF) a = Operation.SELF_TERM_ARRAY; } Operation o = Operation.make(operator, a, true); return o; } } //if no opener, parse the term return parseAtomicTerm(s); } // private static void showWarning(String message) { // new TemporaryFrame( message + "\n( the faulty line has been kept in the addInput window )", // 40000, TemporaryFrame.WARNING ); // } /** * Parse a Term that has no internal structure. * <p> * The Term can be a constant or a variable. * * @param s0 the String to be parsed * @throws nars.io.StringParser.InvalidInputException the String cannot be * parsed into a Term * @return the Term generated from the String */ private Term parseAtomicTerm(String s0) throws InvalidInputException { String s = s0.trim(); if (s.length() == 0) { throw new InvalidInputException("missing term"); } Operator op = memory.getOperator(s0); if(op != null) { return op; } if (s.contains(" ")) { // invalid characters in a name throw new InvalidInputException("invalid term"); } char c = s.charAt(0); if (c == Symbols.INTERVAL_PREFIX) { return Interval.interval(s); } if (containVar(s)) { return new Variable(s); } else { return Term.get(s); } } /** * Parse a String to create a Statement. * * @return the Statement generated from the String * @param s0 The addInput String to be parsed * @throws nars.io.StringParser.InvalidInputException the String cannot be * parsed into a Term */ private Statement parseStatement(String s0) throws InvalidInputException { String s = s0.trim(); int i = topRelation(s); if (i < 0) { throw new InvalidInputException("invalid statement: topRelation(s) < 0"); } String relation = s.substring(i, i + 3); Term subject = parseTerm(s.substring(0, i)); Term predicate = parseTerm(s.substring(i + 3)); Statement t = make(getRelation(relation), subject, predicate, false, 0); if (t == null) { throw new InvalidInputException("invalid statement: statement unable to create: " + getOperator(relation) + " " + subject + " " + predicate); } return t; } /** * Parse a String to create a CompoundTerm. * * @return the Term generated from the String * @param s0 The String to be parsed * @throws nars.io.StringParser.InvalidInputException the String cannot be * parsed into a Term */ private Term parseCompoundTerm(final String s0) throws InvalidInputException { String s = s0.trim(); if (s.isEmpty()) { throw new InvalidInputException("Empty compound term: " + s); } int firstSeparator = s.indexOf(ARGUMENT_SEPARATOR); if (firstSeparator == -1) { throw new InvalidInputException("Invalid compound term (missing ARGUMENT_SEPARATOR): " + s); } String op = (firstSeparator < 0) ? s : s.substring(0, firstSeparator).trim(); NativeOperator oNative = getOperator(op); Operator oRegistered = memory.getOperator(op); if ((oRegistered==null) && (oNative == null)) { throw new InvalidInputException("Unknown operator: " + op); } ArrayList<Term> arg = (firstSeparator < 0) ? new ArrayList<>(0) : parseArguments(s.substring(firstSeparator + 1) + ARGUMENT_SEPARATOR); Term[] argA = arg.toArray(new Term[arg.size()]); Term t; if (oNative!=null) { t = Terms.term(oNative, argA); } else if (oRegistered!=null) { t = make(oRegistered, argA, true); } else { throw new InvalidInputException("Invalid compound term"); } return t; } /** * Parse a String into the argument get of a CompoundTerm. * * @return the arguments in an ArrayList * @param s0 The String to be parsed * @throws nars.io.StringParser.InvalidInputException the String cannot be * parsed into an argument get */ private ArrayList<Term> parseArguments(String s0) throws InvalidInputException { String s = s0.trim(); ArrayList<Term> list = new ArrayList<>(); int start = 0; int end = 0; Term t; while (end < s.length() - 1) { end = nextSeparator(s, start); if (end == start) break; t = parseTerm(s.substring(start, end)); // recursive call list.add(t); start = end + 1; } if (list.isEmpty()) { throw new InvalidInputException("null argument"); } return list; } /* ---------- locate top-level substring ---------- */ /** * Locate the first top-level separator in a CompoundTerm * * @return the index of the next seperator in a String * @param s The String to be parsed * @param first The starting index */ private static int nextSeparator(String s, int first) { int levelCounter = 0; int i = first; while (i < s.length() - 1) { if (isOpener(s, i)) { levelCounter++; } else if (isCloser(s, i)) { levelCounter--; } else if (s.charAt(i) == ARGUMENT_SEPARATOR) { if (levelCounter == 0) { break; } } i++; } return i; } /** * locate the top-level getRelation in a statement * * @return the index of the top-level getRelation * @param s The String to be parsed */ private static int topRelation(final String s) { // need efficiency improvement int levelCounter = 0; int i = 0; while (i < s.length() - 3) { // don't need to check the last 3 characters if ((levelCounter == 0) && (isRelation(s.substring(i, i + 3)))) { return i; } if (isOpener(s, i)) { levelCounter++; } else if (isCloser(s, i)) { levelCounter--; } i++; } return -1; } /* ---------- recognize symbols ---------- */ /** * Check CompoundTerm opener symbol * * @return if the given String is an opener symbol * @param s The String to be checked * @param i The starting index */ private static boolean isOpener(final String s, final int i) { char c = s.charAt(i); boolean b = (getOpener(c)!=null); if (!b) return false; return i + 3 > s.length() || !isRelation(s.substring(i, i + 3)); } /** * Check CompoundTerm closer symbol * * @return if the given String is a closer symbol * @param s The String to be checked * @param i The starting index */ private static boolean isCloser(String s, int i) { char c = s.charAt(i); boolean b = (getCloser(c)!=null); if (!b) return false; return i < 2 || !isRelation(s.substring(i - 2, i + 1)); } public static boolean possiblyNarsese(String s) { if(!s.contains("(") && !s.contains(")") && !s.contains("<") && !s.contains(">")) { return true; } return false; } }