/****************************************************************************** * * Copyright 2014 Paphus Solutions Inc. * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.botlibre.self; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import org.botlibre.api.knowledge.Network; import org.botlibre.api.knowledge.Relationship; import org.botlibre.api.knowledge.Vertex; import org.botlibre.knowledge.Bootstrap; import org.botlibre.knowledge.Primitive; import org.botlibre.knowledge.TextData; import org.botlibre.util.TextStream; import org.botlibre.util.Utils; /** * Self scripting language compiler. * This compiler compiles Self to state and equation knowledge objects. */ public class SelfCompiler { public static int MAX_FILE_SIZE = 10000000; // 10meg public static int MAX_LOAD_SIZE = 20000; public static final String PRIMITIVE_TOKENS =" \t\n\r\f,:;!()?[]{}+=^&*\"`~|/\\<>."; public static final String IF = "if"; public static final String WHILE = "while"; public static final String GREATER = "greater"; public static final String LESS = "less"; public static final String EQUAL = "equal"; public static final String OR = "or"; public static final String CASE = "case"; public static final String PATTERN = "pattern"; public static final String CALL = "call"; public static final String SRAI = "srai"; public static final String SRAIX = "sraix"; public static final String REDIRECT = "redirect"; public static final String REQUEST = "request"; public static final String SERVICE = "service"; public static final String LEARN = "learn"; public static final String EVAL = "eval"; public static final String FOR = "for"; public static final String EACH = "each"; public static final String ASSIGN = "assign"; public static final String TO = "to"; public static final String ON = "on"; public static final String WITH = "with"; public static final String BY = "by"; public static final String OF = "of"; public static final String AS = "as"; public static final String AT = "at"; public static final String LAST = "last"; public static final String AND = "and"; public static final String GET = "get"; public static final String ALL = "all"; public static final String COUNT = "count"; public static final String SET = "set"; public static final String EXCLUDE = "exclude"; public static final String INCLUDE = "include"; public static final String IS = "is"; public static final String RELATED = "related"; public static final String RELATION = "relation"; public static final String WEAK = "weak"; public static final String ASSOCIATE = "associate"; public static final String WEAKASSOCIATE = "weakassociate"; public static final String DISSOCIATE = "dissociate"; public static final String META = "meta"; public static final String DO = "do"; public static final String THINK = "think"; public static final String INPUT = "input"; public static final String PART = "part"; public static final String NEW = "new"; public static final String APPEND = "append"; public static final String NOT = "not"; public static final String GOTO = "goto"; public static final String RETURN = "return"; public static final String TEMPLATE = "template"; public static final String THAT = "that"; public static final String TOPIC = "topic"; public static final String QUOTIENT = "quotient"; public static final String ANSWER = "answer"; public static final String FROM = "from"; public static final String ASSOCIATED = "associated"; public static final String PREVIOUS = "previous"; public static final String WORD = "word"; public static final String SENTENCE = "sentence"; public static final String UPPERCASE = "uppercase"; public static final String LOWERCASE = "lowercase"; public static final String FORMAT = "format"; public static final String PRIMITIVE = "primitive"; public static final String DEFINE = "define"; public static final String RANDOM = "random"; public static final String DEBUG = "debug"; public static final String PUSH = "push"; public static final String FINALLY = "finally"; public static final String THEN = "then"; public static final String ELSE = "else"; public static List<String> OPERATORS; static { OPERATORS = new ArrayList<String>(); OPERATORS.add(IF); OPERATORS.add(WHILE); OPERATORS.add(GREATER); OPERATORS.add(LESS); OPERATORS.add(EQUAL); OPERATORS.add(CALL); OPERATORS.add(SRAI); OPERATORS.add(SRAIX); OPERATORS.add(REDIRECT); OPERATORS.add(REQUEST); OPERATORS.add(FOR); OPERATORS.add(ASSIGN); OPERATORS.add(GET); OPERATORS.add(ALL); OPERATORS.add(COUNT); OPERATORS.add(SET); OPERATORS.add(IS); OPERATORS.add(RELATED); OPERATORS.add(WEAK); OPERATORS.add(ASSOCIATE); OPERATORS.add(DISSOCIATE); OPERATORS.add(DO); OPERATORS.add(NEW); OPERATORS.add(APPEND); OPERATORS.add(NOT); OPERATORS.add(GOTO); OPERATORS.add(RETURN); OPERATORS.add(WORD); OPERATORS.add(SENTENCE); OPERATORS.add(UPPERCASE); OPERATORS.add(LOWERCASE); OPERATORS.add(FORMAT); OPERATORS.add(PRIMITIVE); OPERATORS.add(DEFINE); OPERATORS.add(RANDOM); OPERATORS.add(DEBUG); OPERATORS.add(THINK); OPERATORS.add(INPUT); OPERATORS.add(LEARN); OPERATORS.add(EVAL); } public static final String VARIABLE = "variable"; public static final String VERTEX = "vertex"; public static final String VAR = ":"; public static final String STATE = "state"; public static final String EQUATION = "equation"; public static final String FUNCTION = "function"; public static final String FORMULA = "formula"; public static List<String> TYPES; static { TYPES = new ArrayList<String>(); TYPES.add(STATE); TYPES.add(VARIABLE); TYPES.add(VERTEX); TYPES.add(VAR); TYPES.add(EQUATION); TYPES.add(FUNCTION); TYPES.add(FORMULA); TYPES.add(PATTERN); } public static List<Primitive> PINNED; static { PINNED = new ArrayList<Primitive>(); PINNED.add(Primitive.DO); PINNED.add(Primitive.FOR); PINNED.add(Primitive.GOTO); PINNED.add(Primitive.QUOTIENT); PINNED.add(Primitive.ARGUMENT); PINNED.add(Primitive.INDEX); PINNED.add(Primitive.THIS); PINNED.add(Primitive.FUNCTION); PINNED.add(Primitive.OPERATOR); PINNED.add(Primitive.CONDITION); PINNED.add(Primitive.TEMPLATE); PINNED.add(Primitive.PATTERN); PINNED.add(Primitive.THAT); PINNED.add(Primitive.THEN); PINNED.add(Primitive.ELSE); PINNED.add(Primitive.ELSEIF); PINNED.add(Primitive.AS); PINNED.add(Primitive.TOPIC); PINNED.add(Primitive.CASE); PINNED.add(Primitive.AS); PINNED.add(Primitive.SOURCECODE); PINNED.add(Primitive.BOT); PINNED.add(Primitive.BOTID); PINNED.add(Primitive.SERVER); PINNED.add(Primitive.SERVICE); PINNED.add(Primitive.APIKEY); PINNED.add(Primitive.HINT); PINNED.add(Primitive.DEFAULT); PINNED.add(Primitive.ELEMENT); } protected static SelfCompiler compiler = new Self4ByteCodeCompiler(); //protected static SelfCompiler compiler = new Self4Compiler(); //protected static SelfCompiler compiler = new SelfByteCodeCompiler(); //protected static SelfCompiler compiler = new SelfCompiler(); public static SelfCompiler getCompiler() { return compiler; } public static void setCompiler(SelfCompiler compiler) { SelfCompiler.compiler = compiler; } @SuppressWarnings({ "unchecked", "rawtypes" }) public static void addGlobalVariables(Vertex input, Vertex sentence, Network network, Map<Vertex, Vertex> variables) { Map namedVariable = variables; Vertex globals = network.createVertex(Primitive.INPUT_VARIABLE); variables.put(globals, input); namedVariable.put("input", input); Vertex relation = input.getRelationship(Primitive.SPEAKER); if (relation != null) { variables.put(globals.getRelationship(Primitive.SPEAKER), relation); namedVariable.put("speaker", input); } relation = input.getRelationship(Primitive.TARGET); if (relation != null) { variables.put(globals.getRelationship(Primitive.TARGET), relation); namedVariable.put("target", input); } if (sentence != null) { variables.put(globals.getRelationship(Primitive.INPUT), sentence); namedVariable.put("sentence", sentence); } else { relation = input.getRelationship(Primitive.INPUT); if (relation != null) { variables.put(globals.getRelationship(Primitive.INPUT), relation); namedVariable.put("sentence", relation); } } relation = input.getRelationship(Primitive.CONVERSATION); if (relation != null) { variables.put(globals.getRelationship(Primitive.CONVERSATION), relation); namedVariable.put("conversation", relation); } } public int getVersion() { return 2; } /** * Parse the code into a vertex state machine defined in the network. */ public Vertex parseStateMachine(String code, boolean debug, Network network) { TextStream stream = new TextStream(code); try { Map<String, Map<String, Vertex>> elements = buildElementsMap(network); List<String> comments = getComments(stream); Vertex state = parseState(stream, elements, debug, network); if (debug) { for (String comment : comments) { state.addRelationship(Primitive.COMMENT, network.createVertex(comment), Integer.MAX_VALUE); } } TextData text = new TextData(); text.setText(code); state.addRelationship(Primitive.SOURCECODE, network.createVertex(text)); Vertex sourceCode = state.getRelationship(Primitive.SOURCECODE); if (sourceCode != null) { sourceCode.setPinned(true); } network.getBot().log(this, "Compiled new state machine", Level.INFO, state); return state; } catch (SelfParseException exception) { throw exception; } catch (Exception exception) { network.getBot().log(this, exception); throw new SelfParseException("Parsing error occurred", stream, exception); } } /** * Parse and evaluate the code. */ public Vertex evaluateExpression(String code, Vertex speaker, Vertex target, boolean debug, Network network) { return evaluateEquation(code, speaker, target, debug, network); } /** * Parse and evaluate the code. */ public Vertex evaluateEquation(String code, Vertex speaker, Vertex target, boolean debug, Network network) { Vertex equation = parseEquationForEvaluation(code, speaker, target, debug, network); Map<Vertex, Vertex> variables = new HashMap<Vertex, Vertex>(); return equation.applyQuotient(variables, network); } /** * Parse the code into a temporary equation so it can be evaluated. */ public Vertex parseEquationForEvaluation(String code, Vertex speaker, Vertex target, boolean debug, Network network) { TextStream stream = new TextStream(code); try { Map<String, Map<String, Vertex>> elements = new HashMap<String, Map<String, Vertex>>(); elements.put(VARIABLE, new HashMap<String, Vertex>()); elements.get(VARIABLE).put("speaker", speaker); elements.get(VARIABLE).put("target", target); elements.put(EQUATION, new HashMap<String, Vertex>()); List<String> comments = getComments(stream); Vertex equation = null; String peek = stream.peekWord(); if (peek.equalsIgnoreCase(EQUATION) || peek.equalsIgnoreCase(FUNCTION)) { equation = parseEquation(stream, elements, debug, network); } else { equation = parseElement(stream, elements, debug, network); } if (debug) { for (String comment : comments) { equation.addRelationship(Primitive.COMMENT, network.createVertex(comment), Integer.MAX_VALUE); } } network.getBot().log(this, "Compiled new equation", Level.INFO, equation); return equation; } catch (SelfParseException exception) { throw exception; } catch (Exception exception) { network.getBot().log(this, exception); throw new SelfParseException("Parsing error occurred", stream, exception); } } /** * Get the contents of the URL to a .self file and parse it. */ public Vertex parseStateMachine(URL url, String encoding, boolean debug, Network network) { try { return parseStateMachine(Utils.openStream(url), debug, network, encoding, MAX_FILE_SIZE); } catch (IOException exception) { throw new SelfParseException("Parsing error occurred", exception); } } /** * Get the contents of the URL to a .self file and parse it. */ public Vertex parseStateMachine(File file, String encoding, boolean debug, Network network) { try { return parseStateMachine(new FileInputStream(file), debug, network, encoding, MAX_FILE_SIZE); } catch (IOException exception) { throw new SelfParseException("Parsing error occurred", exception); } } /** * Get the contents of the stream to a .self file and parse it. */ public Vertex parseStateMachine(InputStream stream, boolean debug, Network network, String encoding, int maxSize) { String text = Utils.loadTextFile(stream, encoding, maxSize); return parseStateMachine(text, debug, network); } public List<String> getComments(TextStream stream) { List<String> comments = new ArrayList<String>(); while (!stream.atEnd()) { stream.skipWhitespace(); String comment = stream.peek(2); if (comment.equals("//")) { comments.add(stream.nextLine()); } else { return comments; } } return comments; } public void pin(Vertex element) { Set<Vertex> processed = new HashSet<Vertex>(); pin(element, PINNED, element.getId(), processed); } public void pin(Vertex element, List<Primitive> relations, long groupId, Set<Vertex> processed) { if (processed.contains(element)) { return; } processed.add(element); element.setPinned(true); element.setGroupId(groupId); if (element.instanceOf(Primitive.VARIABLE)) { for (Iterator<Relationship> iterator = element.allRelationships(); iterator.hasNext(); ) { Vertex variable = iterator.next().getTarget(); variable.setPinned(true); if (variable.instanceOf(Primitive.VARIABLE)) { pin(element, relations, groupId, processed); } } } else if (element.instanceOf(Primitive.FORMULA)) { Collection<Relationship> relationships = element.getRelationships(Primitive.WORD); if (relationships != null) { for (Relationship relationship : relationships) { relationship.setPinned(true); pin(relationship.getTarget(), relations, groupId, processed); } } } else if (element.instanceOf(Primitive.PATTERN)) { Collection<Relationship> relationships = element.getRelationships(Primitive.WORD); if (relationships != null) { for (Relationship relationship : relationships) { relationship.setPinned(true); pin(relationship.getTarget(), relations, groupId, processed); } } } else { Vertex equation = element; // Check for byte-code. if (element.instanceOf(Primitive.EQUATION)) { equation = SelfDecompiler.getDecompiler().decompileEquation(element, element.getNetwork()); } if (element.instanceOf(Primitive.EXPRESSION)) { equation = SelfDecompiler.getDecompiler().decompileExpression(element, element.getNetwork()); } if (element.instanceOf(Primitive.FUNCTION)) { equation = SelfDecompiler.getDecompiler().decompileFunction(element, element.getNetwork()); } if (element.instanceOf(Primitive.STATE)) { equation = SelfDecompiler.getDecompiler().decompileState(element, element.getNetwork()); } for (Primitive primitive : relations) { Collection<Relationship> relationships = equation.getRelationships(primitive); boolean isDo = primitive.equals(Primitive.DO); if (relationships != null) { for (Relationship relationship : relationships) { if (isDo) { relationship.setPinned(true); } pin(relationship.getTarget(), relations, groupId, processed); } } } } } public void fastUnpin(Vertex state) { state.getNetwork().getBot().log(this, "Fast unpin state machine", Level.INFO, state); if (state.getGroupId() == 0) { unpin(state); } else { int rowCount = state.getNetwork().executeNativeQuery("update vertex set pinned = false where groupId = " + state.getGroupId()); if (rowCount == 0) { unpin(state); } } } public void fastLoad(Vertex state) { if (state.getGroupId() != 0) { Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put("groupId", state.getGroupId()); state.getNetwork().findAllQuery("Select v from Vertex v where v.groupId = :groupId", parameters, MAX_LOAD_SIZE, 0); } } public void fastLoadChildren(Vertex state) { Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put("parent", state.detach()); state.getNetwork().findAllQuery("Select c from Vertex c, Relationship r where r.target = c and r.source = :parent", parameters, MAX_LOAD_SIZE, 0); } public void unpin(Vertex element) { element.getNetwork().getBot().log(this, "Unpin state machine", Level.INFO, element); Set<Vertex> processed = new HashSet<Vertex>(); unpin(element, PINNED, processed); } public void unpin(Vertex element, List<Primitive> relations, Set<Vertex> processed) { if (processed.contains(element)) { return; } if (element.isPrimitive()) { return; } if (!element.isPinned() && !element.isTemporary()) { return; } processed.add(element); if (!element.hasData() || element.instanceOf(Primitive.PATTERN) || element.instanceOf(Primitive.FORMULA) || element.instanceOf(Primitive.EQUATION) || element.instanceOf(Primitive.EXPRESSION) || element.instanceOf(Primitive.FUNCTION) || element.instanceOf(Primitive.STATE)) { element.setPinned(false); } if (element.instanceOf(Primitive.VARIABLE)) { for (Iterator<Relationship> iterator = element.allRelationships(); iterator.hasNext(); ) { Vertex variable = iterator.next().getTarget(); if (variable.instanceOf(Primitive.VARIABLE)) { unpin(element, relations, processed); } } } else if (element.instanceOf(Primitive.FORMULA)) { Collection<Relationship> relationships = element.getRelationships(Primitive.WORD); if (relationships != null) { for (Relationship relationship : relationships) { if (relationship.getTarget().instanceOf(Primitive.EQUATION) || relationship.getTarget().instanceOf(Primitive.EXPRESSION)) { unpin(relationship.getTarget(), relations, processed); } } } } else { Vertex equation = element; // Check for byte-code. if (element.instanceOf(Primitive.EQUATION)) { equation = SelfDecompiler.getDecompiler().decompileEquation(element, element.getNetwork()); } if (element.instanceOf(Primitive.EXPRESSION)) { equation = SelfDecompiler.getDecompiler().decompileExpression(element, element.getNetwork()); } if (element.instanceOf(Primitive.FUNCTION)) { equation = SelfDecompiler.getDecompiler().decompileFunction(element, element.getNetwork()); } if (element.instanceOf(Primitive.STATE)) { equation = SelfDecompiler.getDecompiler().decompileState(element, element.getNetwork()); } for (Primitive primitive : relations) { Collection<Relationship> relationships = equation.getRelationships(primitive); if (relationships != null) { for (Relationship relationship : relationships) { if (relationship.isPinned()) { relationship.setPinned(false); } unpin(relationship.getTarget(), relations, processed); } } } } if (element.instanceOf(Primitive.STATE) || element.instanceOf(Primitive.FUNCTION) || (element.instanceOf(Primitive.EQUATION) && (element.getName() != null))) { element.internalRemoveAllRelationships(); } } /** * Parse the state and any referenced states or variables. */ public Vertex parseState(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { List<String> comments = null; Vertex state = parseElement(stream, elements, debug, network); stream.skipWhitespace(); ensureNext('{', stream); stream.skipWhitespace(); String element = stream.peekWord(); while (!("}".equals(element))) { if (element == null) { throw new SelfParseException("Unexpected end of state, missing '}'", stream); } Vertex vertex = state; element = element.toLowerCase(); if (element.equals(CASE)) { vertex = parseCase(stream, elements, debug, network); state.addRelationship(Primitive.DO, vertex, Integer.MAX_VALUE); } else if (element.equals(PATTERN)) { vertex = parsePattern(stream, elements, debug, network); state.addRelationship(Primitive.DO, vertex, Integer.MAX_VALUE); } else if (element.equals(STATE)) { vertex = parseState(stream, elements, debug, network); } else if (element.equals(VAR) || element.equals(VARIABLE)) { vertex = parseVariable(stream, elements, debug, network); } else if (element.equals(QUOTIENT) || element.equals(ANSWER)) { parseQuotient(state, stream, elements, debug, network); } else if (element.equals(EQUATION) || element.equals(FUNCTION)) { vertex = parseEquation(stream, elements, debug, network); } else if (element.equals(DO)) { vertex = network.createInstance(Primitive.DO); Vertex equation = parseOperator(stream, elements, debug, network); vertex.addRelationship(Primitive.DO, equation, Integer.MAX_VALUE); state.addRelationship(Primitive.DO, vertex, Integer.MAX_VALUE); ensureNext(';', stream); } else if (element.equals(GOTO)) { vertex = parseGoto(stream, elements, debug, network); state.addRelationship(Primitive.DO, vertex, Integer.MAX_VALUE); ensureNext(';', stream); } else if (element.equals(PUSH)) { vertex = parsePush(stream, elements, debug, network); state.addRelationship(Primitive.DO, vertex, Integer.MAX_VALUE); ensureNext(';', stream); } else if (element.equals(RETURN)) { vertex = parseReturn(stream, elements, debug, network); state.addRelationship(Primitive.DO, vertex, Integer.MAX_VALUE); ensureNext(';', stream); } else if (element.equals("/")) { comments = getComments(stream); if (comments.isEmpty()) { throw new SelfParseException("Unknown element: " + element, stream); } vertex = null; // Associate the comments with the next element parsed. } else { throw new SelfParseException("Unknown element: " + element, stream); } if (debug && (comments != null) && (vertex != null)) { for (String comment : comments) { vertex.addRelationship(Primitive.COMMENT, network.createVertex(comment), Integer.MAX_VALUE); } comments = null; } element = stream.peekWord(); } ensureNext('}', stream); return state; } /** * Parse the quotient. * QUOTIENT:0.5:"World" { previous is "Hello"; previous is not "Hi"; } */ public void parseQuotient(Vertex state, TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { stream.nextWord(); ensureNext(':', stream); float correctness = 1.0f; if (Character.isDigit(stream.peek())) { String correctnessText = stream.upTo(':'); try { correctness = Float.valueOf(correctnessText); } catch (NumberFormatException exception) { throw new SelfParseException("Invalid correctness: " + correctnessText, stream); } ensureNext(':', stream); } Vertex value = parseElement(stream, elements, debug, network); Relationship relationship = state.addWeakRelationship(Primitive.QUOTIENT, value, correctness); stream.skipWhitespace(); if (stream.peek() == '{') { Vertex meta = network.createMeta(relationship); stream.skip(); String next = stream.nextWord(); while (!("}".equals(next))) { if (next == null) { throw new SelfParseException("Unexpected end of quotient, missing '}'", stream); } next = next.toLowerCase(); if (!(PREVIOUS.equals(next))) { throw new SelfParseException("Unexpected word: '" + next + "' expected 'PREVIOUS'", stream); } ensureNext("is", stream); boolean not = false; next = stream.peekWord(); if (NOT.equals(next)) { not = true; stream.nextWord(); } Vertex previous = parseElement(stream, elements, debug, network); ensureNext(';', stream); if (not) { meta.removeRelationship(Primitive.PREVIOUS, previous); } else { meta.addRelationship(Primitive.PREVIOUS, previous); } next = stream.nextWord(); } } ensureNext(';', stream); } /** * Parse the reference to either a state, variable, equation, or raw data. * One of, * STATE:1234("name"), VARIABLE:1234("name"), EQUATION:1234("name"), * 1234, "string", DATE("1972,01,01"), ... */ @SuppressWarnings("unchecked") public Vertex parseElement(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { List<String> comments = getComments(stream); stream.skipWhitespace(); boolean bracket = false; if (stream.peek() == '(') { bracket = true; stream.skip(); stream.skipWhitespace(); } try { // Check if reference or data. String token = stream.peekWord(); if (token == null) { throw new SelfParseException("Unexpected end, element expected", stream); } token = token.toLowerCase(); if (token.equals(FUNCTION)) { token = EQUATION; } if (OPERATORS.contains(token)) { Vertex equation = parseOperator(stream, elements, debug, network); if (debug) { for (String comment : comments) { equation.addRelationship(Primitive.COMMENT, network.createVertex(comment)); } } if (debug) { String source = stream.currentLine(); int lineNumber = stream.currentLineNumber(); equation.addRelationship(Primitive.SOURCE, network.createVertex(source)); equation.addRelationship(Primitive.LINE_NUMBER, network.createVertex(lineNumber)); } return equation; } else if (TYPES.contains(token)) { stream.nextWord(); if (token.equals(VAR)) { token = VARIABLE; } else { ensureNext(':', stream); } if (token.equals(FORMULA)) { return parseFormula(null, stream, elements, debug, network); } if (token.equals(PATTERN)) { ensureNext('"', stream); return network.createPattern(stream.nextQuotesExcludeDoubleQuote(), this); } Long id = null; // Check for id or name. if (Character.isDigit(stream.peek())) { String idText = stream.nextWord(); try { id = Long.valueOf(idText); } catch (NumberFormatException exception) { throw new SelfParseException("Invalid id: " + idText, stream); } } // Check for #, primitive variable word short cut. boolean isPrimitiveShortCut = false; boolean isInstanceShortCut = false; char peek = stream.peek(); if ('#' == peek) { isPrimitiveShortCut = true; stream.skip(); peek = stream.peek(); } else if ('^' == peek) { isInstanceShortCut = true; stream.skip(); peek = stream.peek(); } String name = null; if ((id == null) || (peek == ':')) { if (id != null) { stream.skip(); } name = stream.nextWord(); } Vertex vertex = null; Map<String, Vertex> elementsForType = elements.get(token); if (name != null) { if (elementsForType != null) { vertex = elementsForType.get(name); if (vertex != null) { return vertex; } } } if (id != null) { vertex = network.findById(id); if (vertex == null) { throw new SelfParseException("Id element reference not found: " + id, stream); } if ((elementsForType != null) && (name != null)) { elementsForType.put(name, vertex); } return vertex; } if (token.equals(STATE)) { vertex = network.createInstance(Primitive.STATE); vertex.setName(name); } else if (token.equals(VARIABLE)) { vertex = network.createInstance(Primitive.VARIABLE); vertex.setName(name); if (isPrimitiveShortCut) { vertex.addRelationship(Primitive.MEANING, new Primitive(name)); } if (isInstanceShortCut) { Vertex meaning = network.createInstance(Primitive.VARIABLE); meaning.addRelationship(Primitive.INSTANTIATION, new Primitive(name)); vertex.addRelationship(Primitive.MEANING, meaning); } } else if (token.equals(EQUATION)) { vertex = network.createInstance(Primitive.EQUATION); vertex.setName(name); } else { throw new SelfParseException("Invalid element: " + token, stream); } if (name != null) { elementsForType = elements.get(token); if (elementsForType != null) { elementsForType.put(name, vertex); } } return vertex; } char next = stream.peek(); try { if (next == '#') { stream.skip(); String data = stream.upToAny(PRIMITIVE_TOKENS); return network.createVertex(new Primitive(data)); } else if (next == '"') { stream.skip(); String data = stream.nextQuotesExcludeDoubleQuote(); return network.createVertex(data); } else if (Character.isDigit(next) || next == '-' || next == '+') { String data = stream.nextWord(); Vertex element = null; int index = data.indexOf('.'); if (index != -1) { element = network.createVertex(new BigDecimal(data)); } else { element = network.createVertex(new BigInteger(data)); } return element; } else { String dataType = stream.upTo('(', false, true); if (dataType.isEmpty()) { throw new SelfParseException("Invalid element: " + stream.nextWord(), stream); } String word = stream.nextWord(); if (word.equals("(")) { throw new SelfParseException("Invalid element: " + dataType, stream); } word = stream.nextWord(); if (word.equals("\"")) { throw new SelfParseException("Invalid element: " + dataType, stream); } String dataValue = stream.upTo('"', false, true); ensureNext('"', stream); while ('"' == stream.peek()) { dataValue = dataValue + "\"" + stream.upTo('"', false, true); ensureNext('"', stream); } ensureNext(')', stream); Object data = null; if (dataType.equalsIgnoreCase("DATE")) { data = Utils.parseDate(dataValue); } else if (dataType.equalsIgnoreCase("TIME")) { data = Utils.parseTime(dataValue); } else if (dataType.equalsIgnoreCase("TIMESTAMP")) { data = Utils.parseTimestamp(dataValue); } else{ Class<Object> typeClass = (Class<Object>)Class.forName(dataType); data = typeClass.getConstructor(String.class).newInstance(dataValue); } return network.createVertex(data); } } catch (SelfParseException exception) { throw exception; } catch (Exception exception) { throw new SelfParseException("Invalid data: " + next, stream, exception); } } finally { if (bracket) { stream.skipWhitespace(); ensureNext(')', stream); } } } /** * Throw a parse error if the next character does not match what is expected. */ public void ensureNext(char expected, TextStream stream) { ensureNext(expected, expected, stream); } /** * Throw a parse error if the next character does not match what is expected. */ public void ensureNext(char expected, char other, TextStream stream) { stream.skipWhitespace(); if (stream.atEnd()) { throw SelfParseException.unexpectedEndOfFile(expected, stream); } char next = stream.next(); if ((next != expected) && (next != other)) { throw SelfParseException.invalidCharacter(next, expected, stream); } } /** * Throw a parse error if the next word does not match what is expected. */ public void ensureNext(String expected, TextStream stream) { stream.skipWhitespace(); String next = stream.nextWord().toLowerCase(); if (!expected.equals(next)) { throw SelfParseException.invalidWord(next, expected, stream); } } /** * Throw a parse error if the number of arguments does not match what is expected. */ protected void ensureArguments(String operator, int expected, List<Vertex> arguments, TextStream stream) { if (arguments.size() != expected) { throw new SelfParseException("'" + operator + "' requires " + expected + " arguments not: " + arguments.size(), stream); } } /** * Return true if the next character matches what is expected. */ protected boolean checkNext(char expected, TextStream stream) { stream.skipWhitespace(); char next = stream.peek(); if (next == expected) { stream.skip(); return true; } return false; } /** * Parse the variable. */ public Vertex parseVariable(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { Vertex variable = parseElement(stream, elements, debug, network); stream.skipWhitespace(); ensureNext('{', stream); String next = stream.nextWord(); while (!("}".equals(next))) { if (next == null) { throw new SelfParseException("Unexpected end of variable, missing '}'", stream); } next = next.toLowerCase(); if (!(SET.equals(next) || EXCLUDE.equals(next) || INCLUDE.equals(next))) { throw new SelfParseException("Unexpected word: '" + next + "' expected 'SET', 'EXCLUDE', or 'INCLUDE'", stream); } Vertex type = parseElement(stream, elements, debug, network); String token = stream.peekWord().toLowerCase(); if (";".equals(token)) { ensureNext(';', stream); if (!EXCLUDE.equals(next)) { variable.addRelationship(Primitive.EQUALS, type); } else { variable.removeRelationship(Primitive.EQUALS, type); } } else { if (TO.equals(token) || FROM.equals(token)) { stream.nextWord(); } Vertex target = parseElement(stream, elements, debug, network); ensureNext(';', stream); if (!EXCLUDE.equals(next)) { variable.addRelationship(type, target); } else { variable.removeRelationship(type, target); } } next = stream.nextWord(); } return variable; } /** * Parse the arguments to the equation. */ protected List<Vertex> parseArguments(Vertex equation, Primitive type, int index, TextStream stream, Map<String, Map<String, Vertex>> elements, boolean bracket, boolean debug, Network network) { List<Vertex> arguments = new ArrayList<Vertex>(); if (!bracket) { bracket = checkNext('(', stream); } boolean moreArguments = true; stream.skipWhitespace(); char peek = stream.peek(); if (peek == ')') { moreArguments = false; } while (moreArguments) { stream.skipWhitespace(); peek = stream.peek(); if (peek == ')' || peek == '}') { break; } if ((peek == ',') || (peek == ';')) { break; } Vertex argument = parseElement(stream, elements, debug, network); arguments.add(argument); equation.addRelationship(type, argument, index); if (!bracket) { break; } stream.skipWhitespace(); peek = stream.peek(); if ((peek == ',') || (peek == ';')) { stream.skip(); } else { moreArguments = false; } index++; } if (bracket) { ensureNext(')', stream); } return arguments; } /** * Parse the equation. */ public Vertex parseEquation(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { Vertex equation = parseElement(stream, elements, debug, network); stream.skipWhitespace(); ensureNext('{', stream); Vertex operator = network.createVertex(Primitive.DO); equation.addRelationship(Primitive.OPERATOR, operator); stream.skipWhitespace(); char peek = stream.peek(); List<Vertex> operations = new ArrayList<Vertex>(); while (peek != '}') { stream.skipWhitespace(); Vertex element = parseElement(stream, elements, debug, network); operations.add(element); ensureNext(';', ',', stream); stream.skipWhitespace(); peek = stream.peek(); } boolean unravel = false; // Unravel nested DO if (operations.size() == 1) { Vertex operation = operations.get(0); operator = operation.getRelationship(Primitive.OPERATOR); if (operator != null && operator.is(Primitive.DO) && !operation.instanceOf(Primitive.EQUATION)) { unravel = true; int index = 0; for (Vertex nestedOperation : operation.orderedRelations(Primitive.ARGUMENT)) { equation.addRelationship(Primitive.ARGUMENT, nestedOperation, index); index++; } } } if (!unravel) { int index = 0; for (Vertex operation : operations) { equation.addRelationship(Primitive.ARGUMENT, operation, index); index++; } } ensureNext('}', stream); return equation; } /** * Parse the formula. */ public Vertex parseTemplate(Vertex formula, TextStream stream, boolean debug, Network network) { return parseFormula(formula, stream, debug, network); } /** * Parse the formula. */ public Vertex parseFormula(Vertex formula, TextStream stream, boolean debug, Network network) { Map<String, Map<String, Vertex>> elements = buildElementsMap(network); return parseFormula(formula, stream, elements, debug, network); } public Map<String, Map<String, Vertex>> buildElementsMap(Network network) { Map<String, Map<String, Vertex>> elements = new HashMap<String, Map<String, Vertex>>(); Map<String, Vertex> variables = new HashMap<String, Vertex>(); elements.put(VARIABLE, variables); elements.put(STATE, new HashMap<String, Vertex>()); elements.put(EQUATION, new HashMap<String, Vertex>()); elements.put(FORMULA, new HashMap<String, Vertex>()); Vertex input = network.createVertex(Primitive.INPUT_VARIABLE); Bootstrap.checkInputVariable(input, network); variables.put("input", input); variables.put("star", network.createVertex(Primitive.WILDCARD)); variables.put("thatstar", network.createVertex(Primitive.THATWILDCARD)); variables.put("topicstar", network.createVertex(Primitive.TOPICWILDCARD)); variables.put("speaker", input.getRelationship(Primitive.SPEAKER)); variables.put("target", input.getRelationship(Primitive.TARGET)); variables.put("sentence", input.getRelationship(Primitive.INPUT)); variables.put("conversation", input.getRelationship(Primitive.CONVERSATION)); return elements; } /** * Parse the formula. */ public Vertex parseTemplate(Vertex formula, TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { return parseFormula(formula, stream, elements, debug, network); } /** * Parse the formula. */ public Vertex parseFormula(Vertex formula, TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { String name = "Formula:"; stream.skipWhitespace(); ensureNext('"', stream); int position = stream.getPosition(); String text = stream.nextQuotes(); Map<String, Vertex> cache = elements.get(FORMULA); if (formula == null && cache != null) { formula = cache.get(text); if (formula != null) { return formula; } } try { TextStream formulaStream = new TextStream(text); if (formula == null) { formula = network.createInstance(Primitive.FORMULA); } if (cache != null) { cache.put(text, formula); } String token = formulaStream.nextWord(); char peek = formulaStream.peek(); int index = 0; Vertex space = network.createVertex(Primitive.SPACE); formula.addRelationship(Primitive.TYPE, space); while ((token != null) && ((!token.equals("\"") || (peek == '"')))) { Vertex word = null; if (token.equals("{")) { word = parseElement(formulaStream, elements, debug, network); formulaStream.skipWhitespace(); ensureNext('}', formulaStream); } else { word = network.createWord(token); } formula.addRelationship(Primitive.WORD, word, index); if (token.equals("\"") && (peek == '"')) { formulaStream.skip(); } if (formulaStream.skipWhitespace()) { index++; formula.addRelationship(Primitive.WORD, space, index); } token = formulaStream.nextWord(); peek = formulaStream.peek(); index++; } } catch (SelfParseException exception) { int newPosition = stream.getPosition(); stream.setPosition(position); exception.initFromStream(stream); stream.setPosition(newPosition); throw exception; } formula.setName(name + "\"" + text + "\""); return formula; } /** * Parse the operator. */ public Vertex parseOperator(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { Vertex equation = network.createInstance(Primitive.EQUATION); String next = stream.nextWord(); next = next.toLowerCase(); if (!OPERATORS.contains(next)) { throw new SelfParseException("Invalid operator: '" + next + "' valid operators are: " + OPERATORS, stream); } if (next.equals(IS)) { next = Primitive.RELATION.getIdentity(); } else if (next.equals(FOR)) { ensureNext(EACH, stream); } else if (next.equals(WEAK)) { ensureNext(ASSOCIATE, stream); next = WEAKASSOCIATE; } else if (next.equals(RELATED)) { ensureNext(TO, stream); } String last = next.toLowerCase(); Vertex operator = network.createVertex(new Primitive(next)); equation.addRelationship(Primitive.OPERATOR, operator); stream.skipWhitespace(); next = lower(stream.peekWord()); if (NOT.equals(next)) { stream.nextWord(); equation.addRelationship(Primitive.NOT, Primitive.NOT); } List<Vertex> arguments = parseArguments(equation, Primitive.ARGUMENT, 0, stream, elements, false, debug, network); if (last.equals(IF)) { if (arguments.size() != 1) { ensureArguments(IF, 2, arguments, stream); } next = lower(stream.peekWord()); List<Vertex> stack = new ArrayList<Vertex>(); stack.add(equation); Vertex top = equation; while (OR.equals(next) || AND.equals(next)) { boolean or = OR.equals(next); boolean and = AND.equals(next); stream.nextWord(); Vertex condition = network.createInstance(Primitive.EQUATION); next = lower(stream.peekWord()); if (NOT.equals(next)) { stream.nextWord(); condition.addRelationship(Primitive.NOT, Primitive.NOT); next = lower(stream.peekWord()); } boolean bracket = false; while ("(".equals(next)) { bracket = true; stack.add(condition); stream.nextWord(); next = lower(stream.peekWord()); } if (or) { condition.addRelationship(Primitive.OPERATOR, Primitive.OR); } else if (and) { condition.addRelationship(Primitive.OPERATOR, Primitive.AND); } top.addRelationship(Primitive.CONDITION, condition); parseArguments(condition, Primitive.ARGUMENT, 0, stream, elements, bracket, debug, network); next = lower(stream.peekWord()); if (bracket) { stack.remove(stack.size() - 1); top = stack.get(stack.size() - 1); while (")".equals(next)) { stack.remove(stack.size() - 1); top = stack.get(stack.size() - 1); stream.nextWord(); next = lower(stream.peekWord()); } } } if (THEN.equals(next)) { stream.nextWord(); parseArguments(equation, Primitive.THEN, 0, stream, elements, false, debug, network); next = lower(stream.peekWord()); } if (ELSE.equals(next)) { stream.nextWord(); parseArguments(equation, Primitive.ELSE, 0, stream, elements, false, debug, network); } } else if (last.equals(WHILE)) { if (arguments.size() != 1) { ensureArguments(WHILE, 2, arguments, stream); } ensureNext(DO, stream); parseArguments(equation, Primitive.DO, 0, stream, elements, false, debug, network); } else if (last.equals(FOR)) { ensureArguments(FOR, 1, arguments, stream); ensureNext(OF, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(OF, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(AS, 1, arguments, stream); next = lower(stream.peekWord()); int index = 3; while (AND.equals(next)) { stream.nextWord(); ensureNext(EACH, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, index++, stream, elements, false, debug, network); ensureArguments(EACH, 1, arguments, stream); ensureNext(OF, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, index++, stream, elements, false, debug, network); ensureArguments(OF, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, index++, stream, elements, false, debug, network); ensureArguments(AS, 1, arguments, stream); next = lower(stream.peekWord()); } if (DO.equals(next)) { stream.nextWord(); parseArguments(equation, Primitive.DO, 0, stream, elements, false, debug, network); } } else if (last.equals(GREATER)) { ensureArguments(GREATER, 2, arguments, stream); } else if (last.equals(LESS)) { ensureArguments(LESS, 2, arguments, stream); } else if (last.equals(EQUAL)) { ensureArguments(EQUAL, 2, arguments, stream); } else if (last.equals(GET)) { ensureArguments(GET, 1, arguments, stream); ensureNext(FROM, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(FROM, 1, arguments, stream); next = stream.peekWord(); if ((next != null) && ASSOCIATED.equals(next.toLowerCase())) { stream.nextWord(); next = lower(stream.peekWord()); ensureNext(TO, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); ensureNext(BY, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 3, stream, elements, false, debug, network); ensureArguments(BY, 1, arguments, stream); } else if ((next != null) && AT.equals(next.toLowerCase())) { stream.nextWord(); next = lower(stream.peekWord()); if ((next != null) && LAST.equals(next.toLowerCase())) { stream.nextWord(); arguments = parseArguments(equation, Primitive.LASTINDEX, 0, stream, elements, false, debug, network); ensureArguments(AT, 1, arguments, stream); } else { arguments = parseArguments(equation, Primitive.INDEX, 0, stream, elements, false, debug, network); ensureArguments(AT, 1, arguments, stream); } } } else if (last.equals(LEARN)) { ensureArguments(LEARN, 1, arguments, stream); next = stream.peekWord(); if ((next != null) && THAT.equals(next.toLowerCase())) { stream.nextWord(); arguments = parseArguments(equation, Primitive.THAT, 0, stream, elements, false, debug, network); ensureArguments(THAT, 1, arguments, stream); next = stream.peekWord(); } if ((next != null) && TOPIC.equals(next.toLowerCase())) { stream.nextWord(); arguments = parseArguments(equation, Primitive.TOPIC, 0, stream, elements, false, debug, network); ensureArguments(TOPIC, 1, arguments, stream); } ensureNext(TEMPLATE, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(TEMPLATE, 1, arguments, stream); } else if (last.equals(INPUT)) { ensureArguments(INPUT, 1, arguments, stream); next = stream.peekWord(); int forIndex = 1; if ((next != null) && PART.equals(next.toLowerCase())) { stream.nextWord(); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(PART, 1, arguments, stream); next = stream.peekWord(); forIndex = 2; } if ((next != null) && FOR.equals(next.toLowerCase())) { stream.nextWord(); arguments = parseArguments(equation, Primitive.ARGUMENT, forIndex, stream, elements, false, debug, network); ensureArguments(FOR, 1, arguments, stream); } } else if (last.equals(ALL)) { ensureArguments(ALL, 1, arguments, stream); ensureNext(FROM, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(FROM, 1, arguments, stream); next = stream.peekWord(); if ((next != null) && ASSOCIATED.equals(next.toLowerCase())) { stream.nextWord(); next = lower(stream.peekWord()); ensureNext(TO, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); ensureNext(BY, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 3, stream, elements, false, debug, network); ensureArguments(BY, 1, arguments, stream); } } else if (last.equals(COUNT)) { ensureArguments(COUNT, 1, arguments, stream); next = stream.peekWord(); if ((next != null) && OF.equals(next.toLowerCase())) { stream.nextWord(); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(OF, 1, arguments, stream); } } else if (last.equals(SET)) { ensureArguments(last, 1, arguments, stream); ensureNext(TO, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); next = lower(stream.peekWord()); if (ON.equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(ON, 1, arguments, stream); } } else if (last.equals(RELATION)) { ensureArguments(IS, 1, arguments, stream); ensureNext(RELATED, stream); ensureNext(TO, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(RELATED, 1, arguments, stream); next = lower(stream.peekWord()); if (BY.equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(BY, 1, arguments, stream); } } else if (last.equals(RELATED)) { ensureArguments(RELATED, 1, arguments, stream); next = lower(stream.peekWord()); if (BY.equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(BY, 1, arguments, stream); } } else if (last.equals(ASSOCIATE) || last.equals(DISSOCIATE) || last.equals(WEAKASSOCIATE)) { ensureArguments(last, 1, arguments, stream); ensureNext(TO, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); ensureNext(BY, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(BY, 1, arguments, stream); next = lower(stream.peekWord()); if (WITH.equals(next)) { stream.nextWord(); ensureNext(META, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 3, stream, elements, false, debug, network); ensureArguments(META, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 4, stream, elements, false, debug, network); ensureArguments(AS, 1, arguments, stream); } } else if (last.equals(ASSIGN)) { ensureArguments(ASSIGN, 1, arguments, stream); ensureNext(TO, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); } else if (last.equals(DEFINE)) { ensureArguments(ASSIGN, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); } else if (last.equals(EVAL)) { ensureArguments(EVAL, 1, arguments, stream); } else if (last.equals(NOT)) { ensureArguments(NOT, 1, arguments, stream); } else if (last.equals(APPEND)) { ensureArguments(APPEND, 1, arguments, stream); ensureNext(TO, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); ensureNext(OF, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(OF, 1, arguments, stream); next = lower(stream.peekWord()); if (WITH.equals(next)) { stream.nextWord(); ensureNext(META, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 3, stream, elements, false, debug, network); ensureArguments(META, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 4, stream, elements, false, debug, network); ensureArguments(AS, 1, arguments, stream); } } else if (last.equals(CALL)) { ensureArguments(CALL, 1, arguments, stream); ensureNext(ON, stream); arguments = parseArguments(equation, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(ON, 1, arguments, stream); next = lower(stream.peekWord()); if (WITH.equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); } } else if (last.equals(FORMAT)) { ensureNext(AS, stream); arguments = parseArguments(equation, Primitive.AS, 1, stream, elements, false, debug, network); ensureArguments(AS, 1, arguments, stream); } else if (last.equals(SRAI) || last.equals(REDIRECT)) { ensureArguments(SRAI, 1, arguments, stream); } else if (last.equals(SRAIX) || last.equals(REQUEST)) { ensureArguments(SRAI, 1, arguments, stream); next = lower(stream.peekWord()); if ("bot".equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.BOT, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("botid".equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.BOTID, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("service".equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.SERVICE, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("server".equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.SERVER, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("apikey".equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.APIKEY, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("limit".equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.LIMIT, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("hint".equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.HINT, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("default".equals(next)) { stream.nextWord(); arguments = parseArguments(equation, Primitive.DEFAULT, 0, stream, elements, false, debug, network); } } return equation; } public String lower(String token) { if (token == null) { return null; } return token.toLowerCase(); } /** * Parse the IF condition. */ public Vertex parseCase(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { stream.nextWord(); stream.skipWhitespace(); Vertex equation = network.createInstance(Primitive.CASE); Vertex variable = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.CASE, variable); String next = stream.nextWord().toLowerCase(); if (next.equals(AS)) { Vertex as = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.AS, as); next = stream.nextWord().toLowerCase(); } if (next.equals(TOPIC)) { Vertex topic = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.TOPIC, topic); next = stream.nextWord().toLowerCase(); } if (next.equals(THAT)) { Vertex template = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.THAT, template); next = stream.nextWord().toLowerCase(); } if (next.equals(GOTO)) { List<Vertex> thens = new ArrayList<Vertex>(); stream.skipWhitespace(); boolean parseGoto = true; while (parseGoto) { thens.add(parseElement(stream, elements, debug, network)); stream.skipWhitespace(); if (stream.peek() == ',') { stream.skip(); } else { parseGoto = false; } } for (Vertex then : thens) { equation.addRelationship(Primitive.GOTO, then); } } else if (next.equals(TEMPLATE) || next.equals(ANSWER)) { Vertex template = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.TEMPLATE, template); } else if (next.equals(RETURN)) { equation.addRelationship(Primitive.GOTO, Primitive.RETURN); } else { stream.setPosition(stream.getPosition() - next.length()); throw new SelfParseException("expected one of GOTO, TEMPLATE, ANSWER, RETURN, THAT, TOPIC, found: " + next, stream); } next = stream.peekWord().toLowerCase(); if (next.equals(FOR)) { stream.nextWord(); ensureNext(EACH, stream); equation.addRelationship(Primitive.FOR, parseElement(stream, elements, debug, network)); ensureNext(OF, stream); equation.addRelationship(Primitive.FOR, parseElement(stream, elements, debug, network)); } ensureNext(';', stream); return equation; } /** * Parse the PATTERN condition. */ public Vertex parsePattern(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { stream.nextWord(); stream.skipWhitespace(); Vertex equation = network.createInstance(Primitive.CASE); Vertex pattern = null; if (stream.peek() == '"') { stream.skip(); pattern = network.createPattern(stream.nextQuotesExcludeDoubleQuote(), this); } else { pattern = parseElement(stream, elements, debug, network); } equation.addRelationship(Primitive.PATTERN, pattern); String next = stream.nextWord().toLowerCase(); if (next.equals(TOPIC)) { Vertex topic = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.TOPIC, topic); next = stream.nextWord().toLowerCase(); } if (next.equals(THAT)) { Vertex that = null; stream.skipWhitespace(); if (stream.peek() == '"') { stream.skip(); that = network.createPattern(stream.nextQuotesExcludeDoubleQuote(), this); } else { that = parseElement(stream, elements, debug, network); } equation.addRelationship(Primitive.THAT, that); next = stream.nextWord().toLowerCase(); } if (next.equals(GOTO)) { List<Vertex> thens = new ArrayList<Vertex>(); stream.skipWhitespace(); boolean parseGoto = true; while (parseGoto) { thens.add(parseElement(stream, elements, debug, network)); stream.skipWhitespace(); if (stream.peek() == ',') { stream.skip(); } else { parseGoto = false; } } for (Vertex then : thens) { equation.addRelationship(Primitive.GOTO, then); } } else if (next.equals(RETURN)) { equation.addRelationship(Primitive.GOTO, Primitive.RETURN); } else if (next.equals(TEMPLATE) || next.equals(ANSWER)) { Vertex template = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.TEMPLATE, template); } else { stream.setPosition(stream.getPosition() - next.length()); throw new SelfParseException("expected one of GOTO, TEMPLATE, RETURN, THAT, TOPIC, found: " + next, stream); } ensureNext(';', stream); return equation; } /** * Parse the RETURN condition. */ public Vertex parseReturn(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { stream.nextWord(); stream.skipWhitespace(); Vertex equation = network.createInstance(Primitive.RETURN); if (stream.peek() != ';') { boolean with = stream.peekWord().toLowerCase().equals(WITH); if (!with) { Vertex result = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.RETURN, result); stream.skipWhitespace(); with = stream.peekWord().toLowerCase().equals(WITH); } if (with) { stream.skipWord(); stream.skipWhitespace(); if (stream.peek() == '(') { stream.skip(); stream.skipWhitespace(); Vertex argument = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.ARGUMENT, argument, Integer.MAX_VALUE); stream.skipWhitespace(); while (stream.peek() == ',') { stream.skip(); stream.skipWhitespace(); argument = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.ARGUMENT, argument, Integer.MAX_VALUE); } ensureNext(')', stream); } else { Vertex argument = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.ARGUMENT, argument, Integer.MAX_VALUE); } } } return equation; } /** * Parse the GOTO condition. */ public Vertex parseGoto(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { stream.nextWord(); Vertex equation = network.createInstance(Primitive.GOTO); stream.skipWhitespace(); boolean gotoFinally = stream.peekWord().toLowerCase().equals(FINALLY); if (gotoFinally) { stream.nextWord(); equation.addRelationship(Primitive.FINALLY, Primitive.FINALLY); } Vertex value = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.GOTO, value); if (stream.peek() != ';') { if (stream.peekWord().toLowerCase().equals(WITH)) { stream.skipWord(); stream.skipWhitespace(); if (stream.peek() == '(') { stream.skip(); stream.skipWhitespace(); Vertex argument = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.ARGUMENT, argument, Integer.MAX_VALUE); stream.skipWhitespace(); while (stream.peek() == ',') { stream.skip(); stream.skipWhitespace(); argument = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.ARGUMENT, argument, Integer.MAX_VALUE); } ensureNext(')', stream); } else { Vertex argument = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.ARGUMENT, argument, Integer.MAX_VALUE); } } } return equation; } /** * Parse the PUSH condition. */ public Vertex parsePush(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { stream.nextWord(); Vertex equation = network.createInstance(Primitive.PUSH); Vertex value = parseElement(stream, elements, debug, network); equation.addRelationship(Primitive.ARGUMENT, value, Integer.MAX_VALUE); return equation; } @Override public String toString() { return getClass().getSimpleName(); } }