package me.hammerle.code; import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; import java.util.Stack; import java.util.TreeSet; import me.hammerle.exceptions.PreScriptException; import me.hammerle.exceptions.NoSuchMethodException; public class Code implements Comparable { protected final int realLine; protected final int line; private int subline; private final String function; private final int level; private final int pars; private final Object value; private int jump; private Code(String function, int level, int pars, int line, int subline, Object value, int jump, int realLine) { this.function = function; this.level = level; this.pars = pars; this.line = line; this.subline = subline; this.value = value; this.jump = jump; this.realLine = realLine; } public Code(String function, int level, int pars, int line, int subline, int realLine) { this(function.trim(), level, pars, line, subline, null, 0, realLine); } public Code(int level, int pars, int line, int subline, Object value, int realLine) { this(null, level, pars, line, subline, value, 0, realLine); } public void executeFunction(SnuviParser parser, Script sc, Stack stack) { //System.out.println("Executing: " + this.toString()); if(value != null) { if(value.getClass() == Variable.class) { stack.push(sc.getVar(((Variable) value).getName())); return; } stack.push(value); return; } else if(value == null && function == null) { stack.push(value); return; } Object[] input; if(pars > 0) { input = new Object[pars]; for(int i = 0; i < pars; i++) { input[i] = stack.pop(); } } else { input = new Object[0]; } Object output = parser.parseFunction(sc, function, input); if(output != Void.TYPE) { stack.push(output); } } public String getFunction() { return function; } @Override public String toString() { StringBuilder sb = new StringBuilder(">"); for(int i = 1; i < level; i++) { sb.append(">"); } sb.append(" ("); sb.append(line); sb.append(","); sb.append(subline); sb.append(") "); if(value != null) { sb.append("push "); sb.append(value.getClass().getSimpleName()); sb.append(" '"); sb.append(value); sb.append("'"); return sb.toString(); } else if(value == null && function == null) { sb.append("push Null '"); sb.append(value); sb.append("'"); return sb.toString(); } sb.append("function "); sb.append(function); sb.append(" ("); for(int i = 0; i < pars; i++) { sb.append("X,"); } if(pars > 0) { sb.deleteCharAt(sb.length() - 1); } sb.append(")"); if(jump != 0) { sb.append(" ("); sb.append(jump); sb.append(")"); } return sb.toString(); } @Override public int compareTo(Code o) { int i = Integer.compare(line, o.line); if(i == 0) { return Integer.compare(o.subline, subline); } return i; } public int getJumpLine() { return jump; } // ----------------------------------------------------------------------------------- // code builder // ----------------------------------------------------------------------------------- private static SnuviParser parser = null; private static String scriptName = null; private static int sublines = 0; private static int realLines = 0; private static HashMap strings; private static int findEndOfLine(String code, int pos) { int start = pos; int length = code.length(); while(pos < length) { switch(code.charAt(pos)) { case ';': case '{': case '}': return pos; } pos++; } return start; } private static int findStartOfSyntax(StringBuilder code, int pos) { int bracketCounter = 0; while(pos >= 0 && code.charAt(pos) == ' ') { pos--; } while(pos >= 0) { switch(code.charAt(pos)) { case ';': case '{': case '}': if(bracketCounter != 0) { throw new PreScriptException(scriptName, code.substring(pos - 1, Math.min(code.length(), pos + 20)), "unbalanced ()"); } return pos + 1; case ' ': case '+': case '-': case '*': case '/': case '^': case '@': case '=': case '>': case '<': case '!': case '%': case ',': case '&': case '|': if(bracketCounter != 0) { break; } return pos + 1; case ')': bracketCounter++; break; case '(': if(bracketCounter == 0) { return pos + 1; } bracketCounter--; break; } pos--; } return 0; } private static int findEndOfSyntax(StringBuilder code, int pos) { return findEndOfSyntax(code, pos, false); } private static int findEndOfSyntax(StringBuilder code, int pos, boolean b) { int bracketCounter = 0; char c; while(pos < code.length() && code.charAt(pos) == ' ') { pos++; } while(pos < code.length()) { c = code.charAt(pos); if(b && c == '\n') { if(bracketCounter != 0) { pos++; continue; } return pos; } switch(c) { case ';': case '{': case '}': if(bracketCounter != 0) { throw new PreScriptException(scriptName, code.substring(pos - 1, Math.min(code.length(), pos + 20)), "unbalanced ()"); } return pos; case ' ': case '+': case '-': case '*': case '/': case '^': case '@': case '=': case '>': case '<': case '!': case '%': case ',': case '&': case '|': if(bracketCounter != 0) { break; } return pos; case '(': bracketCounter++; break; case ')': if(bracketCounter == 0) { return pos; } bracketCounter--; break; } pos++; } return code.length(); } private static int findChar(char c, StringBuilder code, int pos) { int length = code.length(); while(pos < length) { if(code.charAt(pos) == c) { return pos; } pos++; } return -1; } private static int findSyntax(String find, StringBuilder code, int pos) { int length = code.length(); int add = find.length() + 1; String s; while(pos < length) { s = code.substring(pos, Math.min(pos + add, length)); // additionel check for e.g. difference between + and += if(s.startsWith(find) && !s.startsWith(find + "=") && code.charAt(pos - 1) != '=') { return pos; } pos++; } return -1; } private static int findSyntaxIgnoreString(String find, StringBuilder code, int pos) { int length = code.length(); int add = find.length() + 1; char c; boolean text = false; String s; while(pos < length) { c = code.charAt(pos); if(text && c != '"') { pos++; continue; } if(c == '"') { text = !text; pos++; continue; } s = code.substring(pos, Math.min(pos + add, length)); // additionel check for e.g. difference between + and += if(s.startsWith(find) && !s.startsWith(find + "=") && code.charAt(pos - 1) != '=') { return pos; } pos++; } return -1; } private static void changeBiSyntax(String syntax, String f, StringBuilder sb, boolean b) { int pos = -1; int end; int start; int slength = syntax.length(); StringBuilder newSyntax; while(pos < sb.length()) { pos = findSyntax(syntax, sb, pos + 1); if(pos == -1) { break; } start = findStartOfSyntax(sb, pos - 1); end = findEndOfSyntax(sb, pos + slength); newSyntax = new StringBuilder(f); newSyntax.append("("); if(b) { newSyntax.append("\""); } newSyntax.append(sb.substring(start, pos).trim()); if(b) { newSyntax.append("\""); } if(end > pos) { newSyntax.append(","); newSyntax.append(sb.substring(pos + slength, end).trim()); } newSyntax.append(")"); pos -= end - start - newSyntax.length(); //System.out.println(sb.substring(start, end) + " ===> " + newSyntax); sb.replace(start, end, newSyntax.toString()); } } private static void changeUnSyntax(String syntax, String f, StringBuilder sb) { int pos = -1; int end; int start; int slength = syntax.length(); String first; StringBuilder newSyntax; while(pos < sb.length()) { pos = findSyntax(syntax, sb, pos + 1); if(pos == -1) { break; } start = findStartOfSyntax(sb, pos - 1); end = findEndOfSyntax(sb, pos + slength); newSyntax = new StringBuilder("setvar(\""); first = sb.substring(start, pos).trim(); newSyntax.append(first); newSyntax.append("\","); newSyntax.append(f); newSyntax.append("("); newSyntax.append(first); newSyntax.append(","); newSyntax.append(sb.substring(pos + slength, end).trim()); newSyntax.append("))"); pos -= end - start - newSyntax.length(); sb.replace(start, end, newSyntax.toString()); } } private static String replaceChar(char find, char replacement, String code) { return code.replace(find, replacement); } private static int countChar(char find, String code) { int counter = 0; int pos = 0; while(pos < code.length()) { if(code.charAt(pos) == find) { counter++; } pos++; } return counter; } private static void addKeyWordBrackets(String keyWord, StringBuilder sb) { String with = keyWord + "()"; int length = keyWord.length(); int pos = 0; while(true) { pos = findSyntax(keyWord, sb, pos); if(pos == -1) { break; } if(pos >= 1 && Character.isLetter(sb.charAt(pos - 1))) { pos += length; continue; } if(pos + length < sb.length() && Character.isLetter(sb.charAt(pos + length))) { pos += length; continue; } if(!sb.substring(pos).startsWith(with)) { sb.replace(pos, pos + length, with); } pos += length; } } public static Code[] generate(SnuviParser parser, String scriptName, String code, HashMap gotos) { System.out.println("START GENERATE"); long startTime = System.currentTimeMillis(); Code.scriptName = scriptName; Code.parser = parser; // comments int old = 0; int pos; StringBuilder sb = new StringBuilder(code); while(true) { old = findSyntaxIgnoreString("/*", sb, old); if(old == -1) { break; } pos = findSyntaxIgnoreString("*/", sb, old); if(pos == -1) { throw new PreScriptException(scriptName, sb.substring(old, Math.min(old + 20, sb.length())), "/* without */"); } sb.delete(old, pos + 2); } old = 0; while(true) { old = findSyntaxIgnoreString("//", sb, old); if(old == -1) { break; } pos = findSyntaxIgnoreString("\n", sb, old); if(pos == -1) { sb.delete(old, sb.length()); break; } sb.delete(old, pos + 1); } // end comments // replacing Strings with #... to get rid of " pos = 0; String text; String replacement; int stringIndex = 0; strings = new HashMap<>(); while(pos < sb.length()) { if(sb.charAt(pos) == '"') { old = pos; pos++; while(sb.charAt(pos) != '"') { if(pos >= sb.length()) { throw new PreScriptException(scriptName, sb.substring(old, Math.min(old + 20, sb.length())), "\" without another"); } pos++; } pos++; text = sb.substring(old, pos); //System.out.println("Found String: " + text); replacement = "#" + stringIndex; sb.replace(old, pos, replacement); strings.put(replacement, text); stringIndex++; pos -= (pos - old) - replacement.length(); } pos++; } // end of string replacing // allowing labels without ; old = 0; while(true) { pos = findChar('@', sb, old); if(pos == -1) { break; } pos++; old = pos; pos = findEndOfSyntax(sb, pos, true); if(pos == -1) { break; } if(sb.charAt(pos) != ';') { sb.insert(pos, ';'); } } // key words without () addKeyWordBrackets("else", sb); addKeyWordBrackets("try", sb); addKeyWordBrackets("catch", sb); //System.out.println(sb); // end key words // replacing of function syntax changeBiSyntax("^", "math.pow", sb, false); changeBiSyntax("/", "div", sb, false); changeBiSyntax("%", "math.mod", sb, false); changeBiSyntax("*", "mul", sb, false); changeBiSyntax("-", "sub", sb, false); changeBiSyntax("+", "add", sb, false); changeBiSyntax("==", "equal", sb, false); changeBiSyntax("!=", "notequal", sb, false); changeBiSyntax("<", "less", sb, false); changeBiSyntax(">", "greater", sb, false); changeBiSyntax(">=", "greaterequal", sb, false); changeBiSyntax("<=", "lessequal", sb, false); changeBiSyntax("||", "or", sb, false); changeBiSyntax("&&", "and", sb, false); changeUnSyntax("+=", "add", sb); changeUnSyntax("-=", "sub", sb); changeUnSyntax("/=", "div", sb); changeUnSyntax("*=", "mul", sb); changeBiSyntax("=", "setvar", sb, true); // numbers like -5 turn to sub(,5) --> fixing pos = 0; while(true) { pos = findSyntax("sub(,", sb, pos); if(pos == -1) { break; } sb.insert(pos + 4, '0'); pos++; } code = sb.toString(); //System.out.println(code); // end of substitution // split code into lines // tree for sorting and inserting of code TreeSet tree = new TreeSet(); sublines = 0; realLines = 1; String actual; int length = code.length(); int level = 1; pos = 0; int line = 0; while(pos < length) { old = pos; pos = findEndOfLine(code, pos); actual = code.substring(old, pos); //System.out.println(actual); realLines += countChar('\n', actual); actual = actual.trim(); if(actual.startsWith("@")) { // sets the right layer of the goto, the exact position is set later if(gotos.put(actual.substring(1), line) != null) { throw new PreScriptException(scriptName, realLines, code.substring(old, Math.min(code.length(), pos + 20)), "double goto"); } pos++; continue; } switch(code.charAt(pos)) { case '{': line++; splitFunctions(tree, actual, level, line); level++; break; case '}': level--; line++; sublines++; tree.add(new Code("gotoline", level, 0, line, sublines, realLines)); break; case ';': line++; splitFunctions(tree, actual, level, line); break; } pos++; } // end of code splitting Code[] c = tree.toArray(new Code[tree.size()]); // generating gotos of key words String function; int baseLevel; boolean gotoLine = false; int lastLineChange = 0; line = 0; for(int i = 0; i < c.length; i++) { function = c[i].function; if(c[i].line != line) { line = c[i].line; lastLineChange = i; } if(function == null) { continue; } switch(function) { case "if": case "else": case "try": case "catch": break; case "while": gotoLine = true; break; default: continue; } baseLevel = c[i].level; for(int j = i + 1; j < c.length; j++) { if(c[j].level <= baseLevel) { c[i].jump = j - i; // key word pos - end pos = jump if(gotoLine) { // setting right jump for gotoline of loops gotoLine = false; c[j].jump = lastLineChange - j - 1; } break; } } } // end of key word jumps // end //java.util.Arrays.stream(c).forEach(co -> System.out.println(co.toString())); //gotos.forEach((k, v) -> System.out.println(k + " " + v)); System.out.println("END GENERATE - " + (System.currentTimeMillis() - startTime)); //System.exit(0); return c; } private static int findOpenBracket(String code, int pos) { int length = code.length(); char c; boolean text = false; while(pos < length) { c = code.charAt(pos); if(text && c != '"') { pos++; continue; } switch(c) { case '(': return pos; case '"': text = !text; break; } pos++; } return -1; } private static int findClosingBracket(String code, int pos) { if(pos == -1) { pos++; } int length = code.length(); char c; boolean text = false; int brackets = 0; while(pos < length) { c = code.charAt(pos); if(text && c != '"') { pos++; continue; } switch(c) { case '(': brackets++; break; case ')': brackets--; if(brackets == 0) { return pos; } break; case '"': text = !text; break; } pos++; } return -1; } private static ArrayList splitComma(String code) { ArrayList list = new ArrayList<>(); int length = code.length(); int pos = 0; int old = 0; int brackets = 0; char c; boolean text = false; while(pos < length) { c = code.charAt(pos); if(text && c != '"') { pos++; continue; } switch(c) { case '"': text = !text; break; case '(': brackets++; break; case ')': brackets--; break; case ',': if(brackets == 0) { list.add(code.substring(old, pos)); old = pos + 1; } break; } pos++; } if(old < pos) { list.add(code.substring(old, pos)); } return list; } private static void splitFunctions(TreeSet tree, String f, int level, int line) { f = f.trim(); sublines++; int start = findOpenBracket(f, 0); int end = findClosingBracket(f, start); if((start != -1 || end != -1) && (((start == -1 || end == -1) && start != end) || end != f.length() - 1)) { throw new PreScriptException(scriptName, realLines, f, "unbalanced ()"); } if(start == -1) { tree.add(new Code(level, 0, line, sublines, convertInput(f, true), realLines)); return; } else if(start >= end - 1) { String functionName = f.substring(0, start).trim().toLowerCase(); if(!parser.isRegisteredFunction(functionName)) { throw new NoSuchMethodException(scriptName, realLines, f, functionName); } tree.add(new Code(functionName, level, 0, line, sublines, realLines)); return; } ArrayList splitted = splitComma(f.substring(start + 1, end)); String functionName = f.substring(0, start).trim().toLowerCase(); if(!parser.isRegisteredFunction(functionName)) { throw new NoSuchMethodException(scriptName, realLines, f, functionName); } tree.add(new Code(functionName, level, splitted.size(), line, sublines, realLines)); splitted.forEach(s -> splitFunctions(tree, s, level, line)); } public static Object convertInput(String s, boolean variable) { if(s == null) { return null; } s = s.trim(); if(s.length() == 0) { return ""; } if(s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"') { if(s.length() == 1) { return "\""; } else if(s.charAt(1) == '$') { return s.substring(2, s.length() - 1); } return s.substring(1, s.length() - 1); } else if(s.startsWith("#")) { return convertInput(strings.get(s), variable); } else if(s.equals("true")) { return true; } else if(s.equals("false")) { return false; } else if(s.equals("null")) { return null; } try { return Double.parseDouble(s); } catch(NumberFormatException ex) { if(variable) { return new Variable(s); } return s; } } }