/****************************************************************************** * * 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.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.botlibre.api.knowledge.Network; import org.botlibre.api.knowledge.Vertex; import org.botlibre.knowledge.BinaryData; import org.botlibre.knowledge.Primitive; import org.botlibre.util.TextStream; import org.botlibre.util.Utils; /** * Self scripting language compiler. * This compiler optimizes equations and states to compiled byte-code. */ public class SelfByteCodeCompiler extends SelfCompiler { /** * 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>()); // Create a temporary equation to execute. Vertex equation = network.createTemporyVertex(); equation.addRelationship(Primitive.INSTANTIATION, Primitive.EQUATION); BinaryData byteCode = new BinaryData(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(byteStream); dataStream.writeLong(network.createVertex(Primitive.DO).getId()); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); stream.skipWhitespace(); char peek = stream.peek(); if (peek == '{') { stream.next(); peek = stream.peek(); stream.skipWhitespace(); while (peek != '}') { stream.skipWhitespace(); parseElementByteCode(stream, dataStream, elements, debug, network); ensureNext(';', ',', stream); stream.skipWhitespace(); peek = stream.peek(); } ensureNext('}', stream); } else { parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); if (!stream.atEnd()) { throw new SelfParseException("Unexpect element " + stream.peekWord(), stream); } } dataStream.writeLong(0l); dataStream.writeLong(0l); byteCode.setBytes(byteStream.toByteArray()); equation.setData(byteCode); 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); } } /** * Parse the state and any referenced states or variables. */ public Vertex parseState(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { try { List<String> comments = null; Vertex state = parseElement(stream, elements, debug, network); BinaryData byteCode = new BinaryData(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(byteStream); 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); } element = element.toLowerCase(); if (element.equals(CASE)) { parseCaseByteCode(stream, dataStream, elements, debug, network); } else if (element.equals(PATTERN)) { parsePatternByteCode(stream, dataStream, elements, debug, network); } else if (element.equals(STATE)) { parseState(stream, elements, debug, network); } else if (element.equals(VAR) || element.equals(VARIABLE)) { parseVariable(stream, elements, debug, network); } else if (element.equals(QUOTIENT) || element.equals(ANSWER)) { parseQuotientByteCode(stream, dataStream, elements, debug, network); } else if (element.equals(EQUATION) || element.equals(FUNCTION)) { parseEquation(stream, elements, debug, network); } else if (element.equals(DO)) { parseDoByteCode(stream, dataStream, elements, debug, network); } else if (element.equals(GOTO)) { parseGotoByteCode(stream, dataStream, elements, debug, network); } else if (element.equals(PUSH)) { parsePushByteCode(stream, dataStream, elements, debug, network); } else if (element.equals(RETURN)) { parseReturnByteCode(stream, dataStream, elements, debug, network); } else if (element.equals("/")) { comments = getComments(stream); if (comments.isEmpty()) { throw new SelfParseException("Unknown element: " + element, stream); } } else { throw new SelfParseException("Unknown element: " + element, stream); } element = stream.peekWord(); } ensureNext('}', stream); dataStream.writeLong(0l); byteCode.setBytes(byteStream.toByteArray()); state.setData(byteCode); network.addVertex(state); return state; } catch (IOException exception) { throw new SelfParseException("IO Error", stream, exception); } } /** * Parse the quotient. * QUOTIENT:0.5:"World" { previous is "Hello"; previous is not "Hi"; } */ public void parseQuotientByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { 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); } dataStream.writeLong(network.createVertex(Primitive.QUOTIENT).getId()); dataStream.writeFloat(correctness); parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); if (stream.peek() == '{') { stream.skip(); String next = stream.nextWord(); dataStream.writeLong(network.createVertex(Primitive.PREVIOUS).getId()); 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); next = stream.peekWord(); if (NOT.equals(next)) { dataStream.writeLong(network.createVertex(Primitive.NOT).getId()); stream.nextWord(); } parseElementByteCode(stream, dataStream, elements, debug, network); ensureNext(';', stream); next = stream.nextWord(); } dataStream.writeLong(0l); } dataStream.writeLong(0l); 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 void parseElementByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { 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)) { dataStream.writeLong(network.createVertex(Primitive.EQUATION).getId()); //equation.setName(token); - cannot set name as named functions can return parseOperatorByteCode(dataStream, stream, elements, debug, network); return; } else if (TYPES.contains(token)) { stream.nextWord(); if (token.equals(VAR)) { token = VARIABLE; } else { ensureNext(':', stream); } if (token.equals(FORMULA)) { dataStream.writeLong(parseFormula(null, stream, elements, debug, network).getId()); return; } if (token.equals(PATTERN)) { ensureNext('"', stream); dataStream.writeLong(network.createPattern(stream.nextQuotesExcludeDoubleQuote(), this).getId()); return; } 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) { dataStream.writeLong(vertex.getId()); return; } } } 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); } dataStream.writeLong(vertex.getId()); return; } 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); } } dataStream.writeLong(vertex.getId()); return; } char next = stream.peek(); try { if (next == '#') { stream.skip(); String data = stream.upToAny(PRIMITIVE_TOKENS); dataStream.writeLong(network.createVertex(new Primitive(data)).getId()); return; } else if (next == '"') { stream.skip(); String data = stream.nextQuotesExcludeDoubleQuote(); dataStream.writeLong(network.createVertex(data).getId()); return; } 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)); } dataStream.writeLong(element.getId()); return; } 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); } dataStream.writeLong(network.createVertex(data).getId()); return; } } catch (SelfParseException exception) { throw exception; } catch (Exception exception) { throw new SelfParseException("Invalid data: " + next, stream, exception); } } finally { if (bracket) { stream.skipWhitespace(); ensureNext(')', stream); } } } /** * Parse the arguments to the equation. */ protected int parseArgumentsByteCode(DataOutputStream dataStream, Primitive type, int index, TextStream stream, Map<String, Map<String, Vertex>> elements, boolean bracket, boolean debug, Network network) throws IOException { dataStream.writeLong(network.createVertex(type).getId()); if (!bracket) { bracket = checkNext('(', stream); } boolean moreArguments = true; stream.skipWhitespace(); char peek = stream.peek(); if (peek == ')') { moreArguments = false; } int count = 0; while (moreArguments) { stream.skipWhitespace(); peek = stream.peek(); if (peek == ')' || peek == '}') { break; } if ((peek == ',') || (peek == ';')) { break; } parseElementByteCode(stream, dataStream, elements, debug, network); count++; if (!bracket) { break; } stream.skipWhitespace(); peek = stream.peek(); if ((peek == ',') || (peek == ';')) { stream.skip(); } else { moreArguments = false; } index++; } if (bracket) { ensureNext(')', stream); } dataStream.writeLong(0l); return count; } /** * Parse the equation. */ public Vertex parseEquation(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { try { Vertex equation = parseElement(stream, elements, debug, network); BinaryData byteCode = new BinaryData(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(byteStream); dataStream.writeLong(network.createVertex(Primitive.DO).getId()); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); stream.skipWhitespace(); ensureNext('{', stream); stream.skipWhitespace(); char peek = stream.peek(); while (peek != '}') { stream.skipWhitespace(); parseElementByteCode(stream, dataStream, elements, debug, network); ensureNext(';', ',', stream); stream.skipWhitespace(); peek = stream.peek(); } ensureNext('}', stream); dataStream.writeLong(0l); dataStream.writeLong(0l); byteCode.setBytes(byteStream.toByteArray()); equation.setData(byteCode); network.addVertex(equation); return equation; } catch (IOException exception) { throw new SelfParseException("IO Error", stream, exception); } } /** * Parse the operator. */ public void parseOperatorByteCode(DataOutputStream dataStream, TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { 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)); dataStream.writeLong(operator.getId()); stream.skipWhitespace(); next = lower(stream.peekWord()); if (NOT.equals(next)) { stream.nextWord(); dataStream.writeLong(network.createVertex(Primitive.NOT).getId()); } int arguments = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 0, stream, elements, false, debug, network); if (last.equals(IF)) { if (arguments != 1) { ensureArguments(IF, 2, arguments, stream); } next = lower(stream.peekWord()); while (OR.equals(next) || AND.equals(next)) { boolean or = OR.equals(next); boolean and = AND.equals(next); if (or) { dataStream.writeLong(network.createVertex(Primitive.OR).getId()); } else if (and) { dataStream.writeLong(network.createVertex(Primitive.AND).getId()); } stream.nextWord(); next = lower(stream.peekWord()); if (NOT.equals(next)) { stream.nextWord(); dataStream.writeLong(network.createVertex(Primitive.NOT).getId()); next = lower(stream.peekWord()); } boolean bracket = false; while ("(".equals(next)) { bracket = true; dataStream.writeLong(network.createVertex(Primitive.LEFTBRACKET).getId()); stream.nextWord(); next = lower(stream.peekWord()); } parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 0, stream, elements, bracket, debug, network); next = lower(stream.peekWord()); if (bracket) { while (")".equals(next)) { dataStream.writeLong(network.createVertex(Primitive.RIGHTBRACKET).getId()); stream.nextWord(); next = lower(stream.peekWord()); } } } if (THEN.equals(next)) { stream.nextWord(); parseArgumentsByteCode(dataStream, Primitive.THEN, 0, stream, elements, false, debug, network); next = lower(stream.peekWord()); } if (ELSE.equals(next)) { stream.nextWord(); parseArgumentsByteCode(dataStream, Primitive.ELSE, 0, stream, elements, false, debug, network); } } else if (last.equals(WHILE)) { if (arguments != 1) { ensureArguments(WHILE, 2, arguments, stream); } ensureNext(DO, stream); parseArgumentsByteCode(dataStream, Primitive.DO, 0, stream, elements, false, debug, network); } else if (last.equals(FOR)) { ensureArguments(FOR, 1, arguments, stream); ensureNext(OF, stream); arguments = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(OF, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, index++, stream, elements, false, debug, network); ensureArguments(EACH, 1, arguments, stream); ensureNext(OF, stream); arguments = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, index++, stream, elements, false, debug, network); ensureArguments(OF, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, index++, stream, elements, false, debug, network); ensureArguments(AS, 1, arguments, stream); next = lower(stream.peekWord()); } if (DO.equals(next)) { stream.nextWord(); parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); ensureNext(BY, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.LASTINDEX, 0, stream, elements, false, debug, network); ensureArguments(AT, 1, arguments, stream); } else { arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.TOPIC, 0, stream, elements, false, debug, network); ensureArguments(TOPIC, 1, arguments, stream); } ensureNext(TEMPLATE, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); ensureNext(BY, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); next = lower(stream.peekWord()); if (ON.equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(RELATED, 1, arguments, stream); next = lower(stream.peekWord()); if (BY.equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); ensureNext(BY, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 3, stream, elements, false, debug, network); ensureArguments(META, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(TO, 1, arguments, stream); ensureNext(OF, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 3, stream, elements, false, debug, network); ensureArguments(META, 1, arguments, stream); ensureNext(AS, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 1, stream, elements, false, debug, network); ensureArguments(ON, 1, arguments, stream); next = lower(stream.peekWord()); if (WITH.equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 2, stream, elements, false, debug, network); } } else if (last.equals(FORMAT)) { ensureNext(AS, stream); arguments = parseArgumentsByteCode(dataStream, 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 = parseArgumentsByteCode(dataStream, Primitive.BOT, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("botid".equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, Primitive.BOTID, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("service".equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, Primitive.SERVICE, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("server".equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, Primitive.SERVER, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("apikey".equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, Primitive.APIKEY, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("limit".equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, Primitive.LIMIT, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("hint".equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, Primitive.HINT, 0, stream, elements, false, debug, network); } next = lower(stream.peekWord()); if ("default".equals(next)) { stream.nextWord(); arguments = parseArgumentsByteCode(dataStream, Primitive.DEFAULT, 0, stream, elements, false, debug, network); } } dataStream.writeLong(0l); } /** * Throw a parse error if the number of arguments does not match what is expected. */ protected void ensureArguments(String operator, int expected, int arguments, TextStream stream) { if (arguments != expected) { throw new SelfParseException("'" + operator + "' requires " + expected + " arguments not: " + arguments, stream); } } /** * Parse the CASE condition. */ public void parseCaseByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { stream.nextWord(); stream.skipWhitespace(); dataStream.writeLong(network.createVertex(Primitive.CASE).getId()); Vertex variable = parseElement(stream, elements, debug, network); dataStream.writeLong(variable.getId()); String next = stream.nextWord().toLowerCase(); if (next.equals(AS)) { dataStream.writeLong(network.createVertex(Primitive.AS).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); next = stream.nextWord().toLowerCase(); } if (next.equals(TOPIC)) { dataStream.writeLong(network.createVertex(Primitive.TOPIC).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); next = stream.nextWord().toLowerCase(); } if (next.equals(THAT)) { dataStream.writeLong(network.createVertex(Primitive.THAT).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); next = stream.nextWord().toLowerCase(); } if (next.equals(GOTO)) { dataStream.writeLong(network.createVertex(Primitive.GOTO).getId()); stream.skipWhitespace(); boolean parseGoto = true; while (parseGoto) { parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); if (stream.peek() == ',') { stream.skip(); } else { parseGoto = false; } } dataStream.writeLong(0l); } else if (next.equals(TEMPLATE) || next.equals(ANSWER)) { dataStream.writeLong(network.createVertex(Primitive.TEMPLATE).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); } else if (next.equals(RETURN)) { dataStream.writeLong(network.createVertex(Primitive.GOTO).getId()); dataStream.writeLong(network.createVertex(Primitive.RETURN).getId()); dataStream.writeLong(0l); } 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)) { dataStream.writeLong(network.createVertex(Primitive.FOR).getId()); stream.nextWord(); ensureNext(EACH, stream); parseElementByteCode(stream, dataStream, elements, debug, network); ensureNext(OF, stream); parseElementByteCode(stream, dataStream, elements, debug, network); dataStream.writeLong(0l); } dataStream.writeLong(0l); ensureNext(';', stream); } /** * Parse the PATTERN condition. */ public void parsePatternByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { stream.nextWord(); stream.skipWhitespace(); dataStream.writeLong(network.createVertex(Primitive.CASE).getId()); dataStream.writeLong(network.createVertex(Primitive.PATTERN).getId()); Vertex pattern = null; if (stream.peek() == '"') { stream.skip(); pattern = network.createPattern(stream.nextQuotesExcludeDoubleQuote(), this); dataStream.writeLong(pattern.getId()); } else { parseElementByteCode(stream, dataStream, elements, debug, network); } String next = stream.nextWord().toLowerCase(); if (next.equals(TOPIC)) { dataStream.writeLong(network.createVertex(Primitive.TOPIC).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); next = stream.nextWord().toLowerCase(); } if (next.equals(THAT)) { dataStream.writeLong(network.createVertex(Primitive.THAT).getId()); Vertex that = null; stream.skipWhitespace(); if (stream.peek() == '"') { stream.skip(); that = network.createPattern(stream.nextQuotesExcludeDoubleQuote(), this); dataStream.writeLong(that.getId()); } else { parseElementByteCode(stream, dataStream, elements, debug, network); } next = stream.nextWord().toLowerCase(); } if (next.equals(GOTO)) { dataStream.writeLong(network.createVertex(Primitive.GOTO).getId()); stream.skipWhitespace(); boolean parseGoto = true; while (parseGoto) { parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); if (stream.peek() == ',') { stream.skip(); } else { parseGoto = false; } } dataStream.writeLong(0l); } else if (next.equals(RETURN)) { dataStream.writeLong(network.createVertex(Primitive.GOTO).getId()); dataStream.writeLong(network.createVertex(Primitive.RETURN).getId()); dataStream.writeLong(0l); } else if (next.equals(TEMPLATE) || next.equals(ANSWER)) { dataStream.writeLong(network.createVertex(Primitive.TEMPLATE).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); } else { stream.setPosition(stream.getPosition() - next.length()); throw new SelfParseException("expected one of GOTO, TEMPLATE, RETURN, THAT, TOPIC, found: " + next, stream); } dataStream.writeLong(0l); ensureNext(';', stream); } /** * Parse the RETURN condition. */ public void parseReturnByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { stream.nextWord(); stream.skipWhitespace(); dataStream.writeLong(network.createVertex(Primitive.RETURN).getId()); if (stream.peek() != ';') { boolean with = stream.peekWord().toLowerCase().equals(WITH); if (!with) { parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); with = stream.peekWord().toLowerCase().equals(WITH); } if (with) { stream.skipWord(); stream.skipWhitespace(); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); if (stream.peek() == '(') { stream.skip(); stream.skipWhitespace(); parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); while (stream.peek() == ',') { stream.skip(); stream.skipWhitespace(); parseElementByteCode(stream, dataStream, elements, debug, network); } ensureNext(')', stream); } else { parseElementByteCode(stream, dataStream, elements, debug, network); } dataStream.writeLong(0l); } } dataStream.writeLong(0l); ensureNext(';', stream); } /** * Parse the GOTO condition. */ public void parseGotoByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { stream.nextWord(); dataStream.writeLong(network.createVertex(Primitive.GOTO).getId()); stream.skipWhitespace(); boolean gotoFinally = stream.peekWord().toLowerCase().equals(FINALLY); if (gotoFinally) { stream.nextWord(); dataStream.writeLong(network.createVertex(Primitive.FINALLY).getId()); } parseElementByteCode(stream, dataStream, elements, debug, network); if (stream.peek() != ';') { if (stream.peekWord().toLowerCase().equals(WITH)) { dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); stream.skipWord(); stream.skipWhitespace(); if (stream.peek() == '(') { stream.skip(); stream.skipWhitespace(); parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); while (stream.peek() == ',') { stream.skip(); stream.skipWhitespace(); parseElementByteCode(stream, dataStream, elements, debug, network); } ensureNext(')', stream); dataStream.writeLong(0l); } else { parseElementByteCode(stream, dataStream, elements, debug, network); dataStream.writeLong(0l); } } } dataStream.writeLong(0l); ensureNext(';', stream); } /** * Parse the PUSH condition. */ public void parsePushByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { stream.nextWord(); dataStream.writeLong(network.createVertex(Primitive.PUSH).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); ensureNext(';', stream); } /** * Parse the DO condition. */ public void parseDoByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { dataStream.writeLong(network.createVertex(Primitive.DO).getId()); parseOperatorByteCode(dataStream, stream, elements, debug, network); ensureNext(';', stream); } }