/****************************************************************************** * * 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.knowledge.TextData; import org.botlibre.util.TextStream; import org.botlibre.util.Utils; /** * Self scripting language compiler. * This compiler optimizes states, functions and expressions to compiled byte-code. */ public class Self4ByteCodeCompiler extends Self4Compiler { /** * Parse the code into a temporary expression so it can be evaluated. */ @Override public Vertex parseExpressionForEvaluation(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>()); getComments(stream); // Create a temporary equation to execute. Vertex expression = network.createTemporyVertex(); expression.addRelationship(Primitive.INSTANTIATION, Primitive.EXPRESSION); BinaryData byteCode = new BinaryData(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(byteStream); dataStream.writeLong(network.createVertex(Primitive.EXPRESSION).getId()); dataStream.writeLong(network.createVertex(Primitive.DO).getId()); dataStream.writeLong(network.createVertex(Primitive.DO).getId()); stream.skipWhitespace(); parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); while (stream.peek() == ';') { stream.skip(); stream.skipWhitespace(); if (stream.atEnd()) { break; } 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); dataStream.writeLong(0l); byteCode.setBytes(byteStream.toByteArray()); expression.setData(byteCode); network.getBot().log(this, "Compiled new expression for evaluation", Level.INFO, expression); return expression; } 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. */ @Override public Vertex parseState(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { try { List<String> comments = null; String next = stream.nextWord(); if (next == null || !next.equalsIgnoreCase("state")) { throw new SelfParseException("Expecting state not: " + next, stream); } Vertex state = parseElementName(Primitive.STATE, stream, elements, debug, network); if (!elements.containsKey("root")) { HashMap<String, Vertex> root = new HashMap<String, Vertex>(1); root.put("root", state); elements.put("root", root); } BinaryData byteCode = new BinaryData(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(byteStream); dataStream.writeLong(network.createVertex(Primitive.SELF4).getId()); 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(ANSWER)) { parseAnswerByteCode(stream, dataStream, elements, debug, network); } else if (element.equals(FUNCTION) || element.equals(EQUATION)) { parseFunctionByteCode(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 code into a vertex state machine defined in the network. */ @Override 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); stream.skipWhitespace(); Vertex state = null; if (stream.peek(6).equalsIgnoreCase("state:")) { state = new SelfByteCodeCompiler().parseState(stream, elements, debug, network); state.addRelationship(Primitive.LANGUAGE, network.createVertex(Primitive.SELF)); state.addRelationship(Primitive.LANGUAGE, network.createVertex(Primitive.SELF2)); } else { state = parseState(stream, elements, debug, network); state.addRelationship(Primitive.LANGUAGE, network.createVertex(Primitive.SELF)); state.addRelationship(Primitive.LANGUAGE, network.createVertex(Primitive.SELF4)); } 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 the quotient. * answer:0.5 "World" { previous "Hello"; previous ! "Hi"; } */ public void parseAnswerByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { stream.nextWord(); stream.skipWhitespace(); float correctness = 1.0f; if (stream.peek() == ':') { stream.skip(); stream.skipWhitespace(); if (Character.isDigit(stream.peek())) { String correctnessText = stream.nextWord(); try { correctness = Float.valueOf(correctnessText); } catch (NumberFormatException exception) { throw new SelfParseException("Invalid correctness: " + correctnessText, 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); } 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, expression, or data. * One of, * Object(1234), Symbol(x) * var variable = "value", variable.attribute = "value", function(), function(arg, arg2).attribute = variable * if (value == "value") {}, for () {}, new Array(), new (x, y), variable.function() * 1234, "string", 'string', #primitive, ... */ public Vertex parseElementByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) throws IOException { return parseElementByteCode(stream, dataStream, elements, null, debug, network); } /** * Override to catch expressions in templates, patterns, and other places. * Optimize bytecode if element in an expression. */ @Override public Vertex parseElement(TextStream stream, Map<String, Map<String, Vertex>> elements, Primitive binary, boolean debug, Network network) { try { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(byteStream); Vertex element = parseElementByteCode(stream, dataStream, elements, binary, debug, network); if (element != null) { return element; } Vertex expression = network.createVertex(); expression.addRelationship(Primitive.INSTANTIATION, Primitive.EXPRESSION); BinaryData byteCode = new BinaryData(); dataStream.writeLong(0l); byteCode.setBytes(byteStream.toByteArray()); expression.setData(byteCode); network.addVertex(expression); return expression; } catch (IOException exception) { throw new SelfParseException("IO Error", stream, exception); } } /** * Parse the template. */ @Override public Vertex parseTemplate(Vertex formula, TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { String name = "Template("; stream.skipWhitespace(); ensureNext('"', stream); int position = stream.getPosition(); String text = stream.nextStringWithBracketsDoubleQuotes(); 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("\\") && (peek == '"')) { token = formulaStream.nextWord(); } else if (token.endsWith("\\") && (peek == '"')) { token = token.substring(0, token.length() - 1); } if (token.equals("{")) { try { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteStream2 = null; DataOutputStream dataStream = new DataOutputStream(byteStream); word = parseElementByteCode(formulaStream, dataStream, elements, null, debug, network); boolean bytecode = false; if (word == null) { bytecode = true; } formulaStream.skipWhitespace(); if (formulaStream.peek() == ';') { bytecode = true; byteStream2 = new ByteArrayOutputStream(); DataOutputStream dataStream2 = new DataOutputStream(byteStream2); dataStream2.writeLong(network.createVertex(Primitive.EXPRESSION).getId()); dataStream2.writeLong(network.createVertex(Primitive.DO).getId()); dataStream2.writeLong(network.createVertex(Primitive.DO).getId()); dataStream2.write(byteStream.toByteArray()); while (formulaStream.peek() == ';') { formulaStream.skip(); formulaStream.skipWhitespace(); if (formulaStream.peek() == '}') { break; } parseElementByteCode(formulaStream, dataStream2, elements, null, debug, network); formulaStream.skipWhitespace(); } dataStream2.writeLong(0l); dataStream2.writeLong(0l); dataStream2.writeLong(0l); } if (bytecode) { word = network.createVertex(); word.addRelationship(Primitive.INSTANTIATION, Primitive.EXPRESSION); BinaryData byteCode = new BinaryData(); if (byteStream2 == null) { dataStream.writeLong(0l); byteCode.setBytes(byteStream.toByteArray()); } else { byteCode.setBytes(byteStream2.toByteArray()); } word.setData(byteCode); network.addVertex(word); } } catch (IOException exception) { throw new SelfParseException("IO Error", stream, exception); } ensureNext('}', formulaStream); } else { word = network.createWord(token); } formula.addRelationship(Primitive.WORD, word, index); 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); int column = exception.getColumnNumber(); exception.initFromStream(stream); exception.setColumnNumber(position + column); stream.setPosition(newPosition); throw exception; } formula.setName(name + "\"" + text + "\")"); return formula; } /** * Parse the reference to either a state, variable, expression, or data. * One of, * Object(1234), Symbol(x) * var variable = "value", variable.attribute = "value", function(), function(arg, arg2).attribute = variable * if (value == "value") {}, for () {}, new Array(), new (x, y), variable.function() * 1234, "string", 'string', #primitive, ... */ public Vertex parseElementByteCode(TextStream stream, DataOutputStream dataStream, Map<String, Map<String, Vertex>> elements, Primitive lastBinary, boolean debug, Network network) throws IOException { getComments(stream); stream.skipWhitespace(); int brackets = 0; while (stream.peek() == '(') { lastBinary = null; brackets++; stream.skip(); stream.skipWhitespace(); } Vertex element = null; if (stream.peek() == '[') { stream.skip(); // Parse array. Vertex array = network.createInstance(Primitive.ARRAY); stream.skipWhitespace(); if (stream.peek() == ']') { stream.skip(); array.addRelationship(Primitive.LENGTH, network.createVertex(0)); dataStream.writeLong(array.getId()); return array; } boolean more = true; int index = 0; while (more) { Vertex value = parseElement(stream, elements, debug, network); array.addRelationship(Primitive.ELEMENT, value, index); index++; stream.skipWhitespace(); if (stream.peek() == ',') { stream.skip(); } else { more = false; } } stream.skipWhitespace(); ensureNext(']', stream); // Need to evaluate expressions inside the object. dataStream.writeLong(network.createVertex(Primitive.EXPRESSION).getId()); Vertex operator = network.createVertex(new Primitive(EVALCOPY)); dataStream.writeLong(operator.getId()); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(array.getId()); dataStream.writeLong(0l); dataStream.writeLong(0l); } else if (stream.peek() == '{') { stream.skip(); // Parse object. Vertex object = null; stream.skipWhitespace(); if (stream.peek() == '}') { stream.skip(); object = network.createVertex(); dataStream.writeLong(object.getId()); return object; } boolean more = true; while (more) { String attribute = stream.nextWord(); if (attribute.equals("\"")) { attribute = stream.nextWord(); ensureNext('"', stream); } ensureNext(':', stream); Vertex attributeValue = parseElement(stream, elements, debug, network); attributeValue.getRelationship(Primitive.NULL); if (object == null) { if (attribute.equals("#data")) { object = attributeValue; } else { object = network.createVertex(); object.addRelationship(new Primitive(attribute), attributeValue); } } else { object.addRelationship(new Primitive(attribute), attributeValue); } stream.skipWhitespace(); if (stream.peek() == ',') { stream.skip(); } else { more = false; } } stream.skipWhitespace(); ensureNext('}', stream); // Need to evaluate expressions inside the object. dataStream.writeLong(network.createVertex(Primitive.EXPRESSION).getId()); Vertex operator = network.createVertex(new Primitive(EVALCOPY)); dataStream.writeLong(operator.getId()); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(object.getId()); dataStream.writeLong(0l); dataStream.writeLong(0l); } else { // 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(VAR)) { token = VARIABLE; } if (OPERATORS.contains(token)) { dataStream.writeLong(network.createVertex(Primitive.EXPRESSION).getId()); parseOperatorByteCode(dataStream, stream, elements, debug, network); } else if (token.equals("^")) { stream.nextWord(); element = parseElementName(Primitive.VARIABLE, stream, elements, debug, network); Vertex meaning = network.createInstance(Primitive.VARIABLE); meaning.addRelationship(Primitive.INSTANTIATION, new Primitive(element.getName())); element.addRelationship(Primitive.MEANING, meaning); dataStream.writeLong(element.getId()); } else if (TYPES.contains(token)) { stream.nextWord(); if (token.equals(TEMPLATE)) { stream.skipWhitespace(); ensureNext('(', stream); element = parseTemplate(null, stream, elements, debug, network); dataStream.writeLong(element.getId()); stream.skipWhitespace(); ensureNext(')', stream); } else if (token.equals(PATTERN)) { stream.skipWhitespace(); ensureNext('(', stream); ensureNext('"', stream); element = network.createPattern(stream.nextQuotesExcludeDoubleQuote(), this); dataStream.writeLong(element.getId()); stream.skipWhitespace(); ensureNext(')', stream); } else if (token.equals(VARIABLE)) { element = parseElementName(Primitive.VARIABLE, stream, elements, debug, network); dataStream.writeLong(element.getId()); } else if (token.equals(DATE)) { stream.skipWhitespace(); ensureNext('(', stream); ensureNext('"', stream); String value = stream.nextQuotesExcludeDoubleQuote(); element = network.createVertex(Utils.parseDate(value)); dataStream.writeLong(element.getId()); stream.skipWhitespace(); ensureNext(')', stream); } else if (token.equals(TIME)) { stream.skipWhitespace(); ensureNext('(', stream); ensureNext('"', stream); String value = stream.nextQuotesExcludeDoubleQuote(); element = network.createVertex(Utils.parseTime(value)); dataStream.writeLong(element.getId()); stream.skipWhitespace(); ensureNext(')', stream); } else if (token.equals(TIMESTAMP)) { stream.skipWhitespace(); ensureNext('(', stream); ensureNext('"', stream); String value = stream.nextQuotesExcludeDoubleQuote(); element = network.createVertex(Utils.parseTimestamp(value)); dataStream.writeLong(element.getId()); stream.skipWhitespace(); ensureNext(')', stream); } else if (token.equals(BINARY)) { stream.skipWhitespace(); ensureNext('(', stream); ensureNext('"', stream); String value = stream.nextQuotesExcludeDoubleQuote(); element = network.createVertex(new BinaryData(value)); dataStream.writeLong(element.getId()); stream.skipWhitespace(); ensureNext(')', stream); } else if (token.equals(TEXT)) { stream.skipWhitespace(); ensureNext('(', stream); ensureNext('"', stream); String value = stream.nextQuotesExcludeDoubleQuote(); element = network.createVertex(new TextData(value)); dataStream.writeLong(element.getId()); stream.skipWhitespace(); ensureNext(')', stream); } else { stream.skipWhitespace(); if (stream.peek() != '(') { throw new SelfParseException("Expected '(' in " + token + " declaration", stream); } stream.skip(); 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 " + token + " id: " + idText, stream); } } char peek = stream.peek(); String name = null; if ((id == null) || (peek == ':')) { if (id != null) { stream.skip(); } name = stream.nextWord(); if (name != null && Character.isLetter(name.charAt(0))) { throw new SelfParseException("Invalid " + token + " declaration: " + name, stream); } } Map<String, Vertex> elementsForType = elements.get(token); element = null; if (name != null) { if (elementsForType != null) { element = elementsForType.get(name); if (element != null) { dataStream.writeLong(element.getId()); } } } if (element == null) { if (id != null) { element = network.findById(id); if (element == null) { throw new SelfParseException("Id element reference not found: " + id, stream); } if ((elementsForType != null) && (name != null)) { elementsForType.put(name, element); } dataStream.writeLong(element.getId()); } else if (name != null) { if (token.equals(STATE)) { element = network.createInstance(Primitive.STATE); element.setName(name); } else if (token.equals(VARIABLE)) { element = network.createInstance(Primitive.VARIABLE); element.setName(name); } else if (token.equals(FUNCTION)) { element = network.createInstance(Primitive.FUNCTION); element.setName(name); } else { throw new SelfParseException("Invalid element: " + token, stream); } if (name != null) { elementsForType = elements.get(token); if (elementsForType != null) { elementsForType.put(name, element); } } dataStream.writeLong(element.getId()); } else { throw new SelfParseException("Invalid element: " + token, stream); } } stream.skipWhitespace(); ensureNext(')', stream); } } else { char next = stream.peek(); try { if (next == '#') { stream.skip(); String data = stream.upToAny(PRIMITIVE_TOKENS); element = network.createVertex(new Primitive(data)); dataStream.writeLong(element.getId()); } else if (next == '"') { stream.skip(); String data = stream.nextStringDoubleQuotes(); data = data.replace("\\\"", "\""); element = network.createVertex(data); dataStream.writeLong(element.getId()); } else if (next == '\'') { stream.skip(); String data = stream.nextStringQuotes(); element = network.createVertex(data); data = data.replace("\\\"", "\""); dataStream.writeLong(element.getId()); } else if (Character.isDigit(next) || next == '-' || next == '+') { int position = stream.getPosition(); String data = stream.nextWord(); if (data.indexOf(',') != -1) { stream.setPosition(position); data = stream.upTo(','); } int index = data.indexOf('.'); if ((index != -1) && (index + 1 < data.length())) { // Check for 4.next if (!Character.isDigit(data.charAt(index + 1))) { stream.setPosition(position); data = stream.upTo('.'); } } if (index != -1) { element = network.createVertex(new BigDecimal(data)); } else { element = network.createVertex(new BigInteger(data)); } dataStream.writeLong(element.getId()); } else { element = parseElementName(null, stream, elements, debug, network); dataStream.writeLong(element.getId()); } } catch (SelfParseException exception) { throw exception; } catch (Exception exception) { throw new SelfParseException("Invalid data: " + next, stream, exception); } } } stream.skipWhitespace(); char peek = stream.peek(); Vertex push = network.createVertex(Primitive.PUSH); Vertex pop = network.createVertex(Primitive.POP); while (".=!&|)[<>+-*/".indexOf(peek) != -1) { String operator1 = stream.peek(1); String operator = stream.peek(2); if (peek == ')') { if (brackets > 0) { brackets--; stream.skip(); } else { break; } } else if (peek == '.') { element = null; stream.skip(); int position = stream.getPosition(); String attribute = stream.nextWord(); if (!Character.isAlphabetic(attribute.charAt(0)) && attribute.charAt(0) != '@') { throw new SelfParseException("Invalid attribute name: " + attribute, stream); } if (attribute.indexOf('.') != -1) { stream.setPosition(position); stream.skipWhitespace(); attribute = stream.upTo('.'); } stream.skipWhitespace(); Vertex associate = null; Vertex associateRelationship = null; peek = stream.peek(); if (peek == '(') { dataStream.writeLong(push.getId()); dataStream.writeLong(network.createVertex(Primitive.CALL).getId()); dataStream.writeLong(network.createVertex(Primitive.THIS).getId()); dataStream.writeLong(pop.getId()); dataStream.writeLong(0l); dataStream.writeLong(network.createVertex(Primitive.FUNCTION).getId()); dataStream.writeLong(network.createVertex(new Primitive(attribute)).getId()); dataStream.writeLong(0l); parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 0, stream, elements, false, debug, network); dataStream.writeLong(0l); } else { Vertex index = null; if (peek == '[') { stream.skip(); index = parseElement(stream, elements, debug, network); stream.skipWhitespace(); if (stream.peek() == ',') { associate = index; associateRelationship = parseElement(stream, elements, debug, network); } ensureNext(']', stream); peek = stream.peek(); } boolean isSet = false; String peek2 = stream.peek(2); dataStream.writeLong(push.getId()); if (peek == '=' && peek2.equals("=+")) { isSet = true; stream.skip(2); dataStream.writeLong(network.createVertex(Primitive.ADD).getId()); } else if (peek == '=' && peek2.equals("=-")) { isSet = true; stream.skip(2); dataStream.writeLong(network.createVertex(Primitive.REMOVE).getId()); } else if (peek == '=' && !peek2.equals("==")) { isSet = true; stream.skip(); dataStream.writeLong(network.createVertex(Primitive.SET).getId()); } else { dataStream.writeLong(network.createVertex(Primitive.GET).getId()); } if (index != null) { dataStream.writeLong(network.createVertex(Primitive.INDEX).getId()); dataStream.writeLong(index.getId()); dataStream.writeLong(0l); } dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(pop.getId()); dataStream.writeLong(network.createVertex(new Primitive(attribute)).getId()); if (isSet) { parseElementByteCode(stream, dataStream, elements, debug, network); } if (associate != null) { dataStream.writeLong(associate.getId()); dataStream.writeLong(associateRelationship.getId()); } dataStream.writeLong(0l); dataStream.writeLong(0l); } } else if (peek == '[') { element = null; stream.skip(); Vertex variable = parseElement(stream, elements, debug, network); stream.skipWhitespace(); ensureNext(']', stream); stream.skipWhitespace(); peek = stream.peek(); if (peek == '(') { dataStream.writeLong(push.getId()); dataStream.writeLong(network.createVertex(Primitive.CALL).getId()); dataStream.writeLong(network.createVertex(Primitive.THIS).getId()); dataStream.writeLong(pop.getId()); dataStream.writeLong(0l); dataStream.writeLong(network.createVertex(Primitive.FUNCTION).getId()); dataStream.writeLong(variable.getId()); dataStream.writeLong(0l); parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 0, stream, elements, false, debug, network); dataStream.writeLong(0l); } else { boolean isSet = false; String peek2 = stream.peek(2); dataStream.writeLong(push.getId()); if (peek == '=' && peek2.equals("=+")) { isSet = true; stream.skip(2); dataStream.writeLong(network.createVertex(Primitive.ADD).getId()); } else if (peek == '=' && peek2.equals("=-")) { isSet = true; stream.skip(2); dataStream.writeLong(network.createVertex(Primitive.REMOVE).getId()); } else if (stream.peek() == '=' && !stream.peek(2).equals("==")) { isSet = true; stream.skip(); dataStream.writeLong(network.createVertex(Primitive.SET).getId()); } else { dataStream.writeLong(network.createVertex(Primitive.GET).getId()); } dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(pop.getId()); dataStream.writeLong(variable.getId()); if (isSet) { parseElementByteCode(stream, dataStream, elements, debug, network); } dataStream.writeLong(0l); dataStream.writeLong(0l); } } else { element = null; Primitive operation = BINARY_OPERATORS.get(operator); Primitive operation1 = null; if (operation == null) { operation1 = BINARY_OPERATORS.get(operator1); } if (operator.equals("//")) { break; } else if (operator.equals("++")) { stream.skip(2); dataStream.writeLong(push.getId()); dataStream.writeLong(network.createVertex(Primitive.INCREMENT).getId()); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(pop.getId()); dataStream.writeLong(0l); dataStream.writeLong(0l); } else if (operator.equals("--")) { stream.skip(2); dataStream.writeLong(push.getId()); dataStream.writeLong(network.createVertex(Primitive.DECREMENT).getId()); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(pop.getId()); dataStream.writeLong(0l); dataStream.writeLong(0l); } else if (operation != null || operation1 != null) { if (lastBinary != null) { // Check order of binary operations. int lastIndex = BINARY_PRECEDENCE.indexOf(lastBinary); int index = 0; if (operation == null) { index = BINARY_PRECEDENCE.indexOf(operation1); } else { index = BINARY_PRECEDENCE.indexOf(operation); } if (index <= lastIndex) { break; } } if (operation == null) { stream.skip(); operator = operator1; operation = operation1; } else { stream.skip(2); } dataStream.writeLong(push.getId()); dataStream.writeLong(network.createVertex(operation).getId()); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(pop.getId()); parseElementByteCode(stream, dataStream, elements, operation, debug, network); dataStream.writeLong(0l); dataStream.writeLong(0l); } else if (peek == '=') { stream.skip(); dataStream.writeLong(push.getId()); dataStream.writeLong(network.createVertex(Primitive.ASSIGN).getId()); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(pop.getId()); parseElementByteCode(stream, dataStream, elements, null, debug, network); dataStream.writeLong(0l); dataStream.writeLong(0l); } else { throw new SelfParseException("Invalid operator: " + operator, stream); } } stream.skipWhitespace(); peek = stream.peek(); } stream.skipWhitespace(); while (brackets > 0) { stream.skipWhitespace(); ensureNext(')', stream); brackets--; } return element; } /** * Parse the arguments to the expression. */ protected int parseArgumentsByteCode(DataOutputStream dataStream, Primitive type, int index, TextStream stream, Map<String, Map<String, Vertex>> elements, boolean outerBracket, boolean debug, Network network) throws IOException { dataStream.writeLong(network.createVertex(type).getId()); boolean bracket = false; if (!outerBracket) { 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 && !outerBracket) { break; } stream.skipWhitespace(); peek = stream.peek(); if ((peek == ',') || (peek == ';')) { stream.skip(); } else { String previous = stream.peekPreviousWord(); if (!"}".equals(previous)) { moreArguments = false; } } index++; } if (bracket) { ensureNext(')', stream); } dataStream.writeLong(0l); return count; } /** * Parse the function. */ public Vertex parseFunctionByteCode(TextStream stream, Map<String, Map<String, Vertex>> elements, boolean debug, Network network) { stream.nextWord(); try { Vertex function = parseElementName(Primitive.FUNCTION, stream, elements, debug, network); BinaryData byteCode = new BinaryData(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(byteStream); stream.skipWhitespace(); ensureNext('{', stream); stream.skipWhitespace(); char peek = stream.peek(); while (peek != '}') { stream.skipWhitespace(); parseElementByteCode(stream, dataStream, elements, debug, network); String previous = stream.peekPreviousWord(); stream.skipWhitespace(); if (!"}".equals(previous)) { ensureNext(';', ',', stream); } stream.skipWhitespace(); peek = stream.peek(); while (peek == ';') { stream.skip(); stream.skipWhitespace(); peek = stream.peek(); } } ensureNext('}', stream); dataStream.writeLong(0l); byteCode.setBytes(byteStream.toByteArray()); function.setData(byteCode); network.addVertex(function); return function; } 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); } String last = next.toLowerCase(); if (next.equals(NOT)) { next = "not"; } Vertex operator = network.createVertex(new Primitive(next)); dataStream.writeLong(operator.getId()); if (last.equals(IF)) { int arguments = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 0, stream, elements, false, debug, network); ensureArguments(IF, 1, arguments, stream); stream.skipWhitespace(); ensureNext('{', stream); parseArgumentsByteCode(dataStream, Primitive.THEN, 0, stream, elements, true, debug, network); stream.skipWhitespace(); ensureNext('}', stream); next = lower(stream.peekWord()); boolean elseif = true; while (elseif) { elseif = false; if (ELSE.equals(next)) { stream.nextWord(); next = lower(stream.peekWord()); if (IF.equals(next)) { elseif = true; dataStream.writeLong(network.createVertex(Primitive.ELSEIF).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); dataStream.writeLong(0l); } else { stream.skipWhitespace(); ensureNext('{', stream); parseArgumentsByteCode(dataStream, Primitive.ELSE, 0, stream, elements, true, debug, network); stream.skipWhitespace(); ensureNext('}', stream); } } } } else if (last.equals(WHILE)) { int arguments = parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 0, stream, elements, false, debug, network); ensureArguments(WHILE, 1, arguments, stream); stream.skipWhitespace(); ensureNext('{', stream); parseArgumentsByteCode(dataStream, Primitive.DO, 0, stream, elements, true, debug, network); stream.skipWhitespace(); ensureNext('}', stream); } else if (last.equals(DO) || last.equals(THINK)) { stream.skipWhitespace(); ensureNext('{', stream); parseArgumentsByteCode(dataStream, Primitive.DO, 0, stream, elements, true, debug, network); stream.skipWhitespace(); ensureNext('}', stream); } else if (last.equals(FOR)) { stream.skipWhitespace(); ensureNext('(', stream); boolean more = true; dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); while (more) { parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); ensureNext("in", stream); parseElementByteCode(stream, dataStream, elements, debug, network); stream.skipWhitespace(); if (stream.peek() == ',') { stream.skip(); } else { more = false; } } dataStream.writeLong(0l); ensureNext(')', stream); stream.skipWhitespace(); ensureNext('{', stream); parseArgumentsByteCode(dataStream, Primitive.DO, 0, stream, elements, true, debug, network); stream.skipWhitespace(); ensureNext('}', stream); } else if (last.equals(NEW)) { // Handle new Date() vs new (#human, #thing) stream.skipWhitespace(); if (Character.isUpperCase(stream.peek())) { int position = stream.getPosition(); String type = stream.nextWord(); if (stream.peek() == '(') { // TODO handle constructors. stream.skip(); stream.skipWhitespace(); ensureNext(')', stream); dataStream.writeLong(network.createVertex(Primitive.ARGUMENT).getId()); dataStream.writeLong(network.createVertex(new Primitive(type.toLowerCase())).getId()); dataStream.writeLong(0l); } else { stream.setPosition(position); parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 0, stream, elements, false, debug, network); } } else { parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 0, stream, elements, false, debug, network); } } else { parseArgumentsByteCode(dataStream, Primitive.ARGUMENT, 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()); String next = stream.peekWord(); boolean anyOrNone = false; if (next.equalsIgnoreCase(ANY)) { stream.nextWord(); next = stream.peekWord(); if (next.equalsIgnoreCase(OR)) { stream.nextWord(); stream.skipWhitespace(); ensureNext(NONE, stream); anyOrNone = true; } } Vertex variable = parseElementByteCode(stream, dataStream, elements, debug, network); if (!anyOrNone && variable != null && variable.instanceOf(Primitive.ARRAY)) { variable.addRelationship(Primitive.TYPE, Primitive.REQUIRED); } next = stream.nextWord(); if (next.equalsIgnoreCase(AS)) { dataStream.writeLong(network.createVertex(Primitive.AS).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); next = stream.nextWord(); } if (next.equalsIgnoreCase(TOPIC)) { dataStream.writeLong(network.createVertex(Primitive.TOPIC).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); next = stream.nextWord(); } if (next.equalsIgnoreCase(THAT)) { dataStream.writeLong(network.createVertex(Primitive.THAT).getId()); parseElementByteCode(stream, dataStream, elements, debug, network); next = stream.nextWord(); } if (next.equalsIgnoreCase(GOTO)) { dataStream.writeLong(network.createVertex(Primitive.GOTO).getId()); stream.skipWhitespace(); boolean parseGoto = true; while (parseGoto) { dataStream.writeLong(parseElementName(Primitive.STATE, stream, elements, debug, network).getId()); stream.skipWhitespace(); if (stream.peek() == ',') { stream.skip(); } else { parseGoto = false; } } dataStream.writeLong(0l); } else if (next.equalsIgnoreCase(TEMPLATE) || next.equalsIgnoreCase(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(); if (next.equalsIgnoreCase(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.nextStringWithBracketsDoubleQuotes(), 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.nextStringWithBracketsDoubleQuotes(), 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()); } dataStream.writeLong(parseElementName(Primitive.STATE, stream, elements, debug, network).getId()); 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); } }