package me.hammerle.code; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.Stack; import me.hammerle.exceptions.PreScriptException; import me.hammerle.exceptions.NoSuchMethodException; import me.hammerle.math.Fraction; public class Code { protected final int realLine; protected final String function; protected final int pars; protected Object value; private Code(String function, int pars, Object value, int realLine) { this.function = function; this.pars = pars; this.value = value; this.realLine = realLine; } public Code(String function, int pars, int realLine) { this(function, pars, null, realLine); } public Code(Object value, int realLine) { this(null, 0, value, realLine); } public void executeFunction(SnuviParser parser, Script sc, Stack stack) { if(function == null) { if(value != null) { if(value.getClass() == Variable.class) { stack.push(sc.getVar(((Variable) value).getName())); return; } stack.push(value); return; } 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); } } @Override public String toString() { StringBuilder sb = new StringBuilder("("); sb.append(realLine); sb.append(") "); if(function != null) { 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); } if(value != null) { sb.append("("); sb.append(value); sb.append(")"); } } else { sb.append("push '"); sb.append(value); sb.append("' "); if(value != null) { sb.append(value.getClass().getSimpleName()); } return sb.toString(); } return sb.toString(); } // ----------------------------------------------------------------------------------- // code builder // ----------------------------------------------------------------------------------- private static String scriptName = null; private static int findFirstNonLetterOrDigit(StringBuilder code, int pos) { int length = code.length(); while(pos < length) { if(!Character.isLetterOrDigit(code.charAt(pos))) { return pos; } pos++; } return -1; } 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, countChar('\n', pos, code), "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, countChar('\n', pos, code), "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(); String s = code.toString(); while(pos < length) { // additionel check for e.g. difference between + and += if(s.startsWith(find, pos) && !s.startsWith(find + "=", pos) && (pos == 0 || 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(); char c; boolean text = false; while(pos < length) { c = code.charAt(pos); if(text && c != '"') { pos++; continue; } else if(c == '"') { text = !text; pos++; continue; } else if(code.substring(pos, Math.min(pos + add, length)).equals(find)) { 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()); } } public static int countChar(char find, int end, StringBuilder code) { int counter = 0; int pos = 0; end = Math.min(end, code.length()); while(pos < end) { 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 = sb.indexOf(keyWord, pos); if(pos + length >= sb.length()) { throw new PreScriptException(scriptName, countChar('\n', pos, sb), "unexpected keyword"); } else if(pos == -1 || (pos >= 1 && Character.isLetterOrDigit(sb.charAt(pos - 1))) || (Character.isLetterOrDigit(sb.charAt(pos + length)))) { break; } else if(sb.charAt(pos + length) != '(') { sb.replace(pos, pos + length, with); } pos += length; } } private static String removeNonSyntax(String s) { StringBuilder sb = new StringBuilder(s); int pos = 0; while(pos < sb.length()) { switch(sb.charAt(pos)) { case ' ': case '\t': case '\n': sb.deleteCharAt(pos); continue; } pos++; } return sb.toString(); } public static Code[] generate(SnuviParser parser, String scriptName, String code, HashMap gotos) { System.out.println("START GENERATE"); long startTime = System.currentTimeMillis(); Code.scriptName = scriptName; int old = 0; int pos; StringBuilder sb = new StringBuilder(code); // --------------------------------------------------------------------- // comments // --------------------------------------------------------------------- while(true) { old = findSyntaxIgnoreString("/*", sb, old); if(old == -1) { break; } pos = findSyntaxIgnoreString("*/", sb, old); if(pos == -1) { throw new PreScriptException(scriptName, countChar('\n', old, sb), "/* 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); } // --------------------------------------------------------------------- // replacing Strings with #... to get rid of " // --------------------------------------------------------------------- pos = 0; String replacement; String text; int stringIndex = 0; HashMap strings = new HashMap<>(); while(pos < sb.length()) { if(sb.charAt(pos) == '"') { old = pos; pos++; while(sb.charAt(pos) != '"') { pos++; if(pos >= sb.length()) { throw new PreScriptException(scriptName, countChar('\n', old, sb), "\" without another"); } } pos++; text = sb.substring(old, pos); replacement = "#" + stringIndex; sb.replace(old, pos, replacement); strings.put(replacement, text); stringIndex++; pos -= (pos - old) - replacement.length(); } pos++; } // --------------------------------------------------------------------- // allowing labels without ; // --------------------------------------------------------------------- old = 0; while(true) { pos = findChar('@', sb, old); if(pos == -1) { break; } pos++; old = pos; pos = findFirstNonLetterOrDigit(sb, pos); if(pos == -1) { break; } if(sb.charAt(pos) != ';') { sb.insert(pos, ';'); } } // --------------------------------------------------------------------- // allowing key words without () // --------------------------------------------------------------------- addKeyWordBrackets("else", sb); addKeyWordBrackets("try", sb); addKeyWordBrackets("catch", sb); // --------------------------------------------------------------------- // 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(); // --------------------------------------------------------------------- // split code into lines // --------------------------------------------------------------------- ArrayList lines = new ArrayList<>(); int lineCounter = 1; ArrayList realCodeLine = new ArrayList<>(); old = 0; pos = 0; int length = code.length(); int counter = 0; int bracketCounter = 0; boolean closed = true; Stack brackets = new Stack<>(); while(pos < length) { switch(code.charAt(pos)) { case '(': closed = false; bracketCounter++; break; case ')': bracketCounter--; break; case ';': if(bracketCounter != 0) { throw new PreScriptException(scriptName, lineCounter, "unbalanced ()"); } lines.add(removeNonSyntax(code.substring(old, pos))); realCodeLine.add(lineCounter); old = pos + 1; closed = true; break; case '{': if(bracketCounter != 0) { throw new PreScriptException(scriptName, lineCounter, "unbalanced ()"); } brackets.push(lineCounter); counter++; lines.add(removeNonSyntax(code.substring(old, pos))); realCodeLine.add(lineCounter); old = pos + 1; lines.add("{"); realCodeLine.add(lineCounter); break; case '}': if(!closed) { throw new PreScriptException(scriptName, lineCounter, "missing ;"); } else if(bracketCounter != 0) { throw new PreScriptException(scriptName, lineCounter, "unbalanced ()"); } counter--; if(counter < 0) { throw new PreScriptException(scriptName, lineCounter, "unexpected }"); } old = pos + 1; lines.add("}"); realCodeLine.add(lineCounter); brackets.pop(); break; case '\n': lineCounter++; break; } pos++; } // well the second check should be useless if(counter != 0 && !brackets.isEmpty()) { throw new PreScriptException(scriptName, brackets.pop(), "unbalanced {}"); } // --------------------------------------------------------------------- // generating byte code // --------------------------------------------------------------------- ArrayList co = new ArrayList<>(); Stack parCounter = new Stack<>(); char current; char previous; int layer = 0; int line; boolean noFunction; LinkedList whileStart = new LinkedList<>(); String s; for(int i = 0; i < lines.size(); i++) { s = lines.get(i); if(s.isEmpty()) { continue; } else if(s.startsWith("while")) { whileStart.push(co.size()); } else if(s.charAt(0) == '@') { gotos.put(s.substring(1), co.size()); continue; } line = realCodeLine.get(i); previous = ','; noFunction = true; pos = s.length() - 1; while(pos >= 0) { current = s.charAt(pos); switch(current) { case ')': parCounter.add(0); if(previous != ',' && previous != ')') { throw new PreScriptException(scriptName, line, "unexpected )"); } old = pos; break; case '(': if(previous != ')') { parCounter.set(parCounter.size() - 1, parCounter.lastElement() + 1); if(noFunction) { co.add(new Code(Code.convertInput(strings, s.substring(pos + 1, old), true), line)); } } old = pos; pos--; while(pos >= 0 && (Character.isLetterOrDigit(s.charAt(pos)) || s.charAt(pos) == '_' || s.charAt(pos) == '.')) { pos--; } pos++; String functionName = s.substring(pos, old).toLowerCase(); if(!parser.isRegisteredFunction(functionName)) { throw new NoSuchMethodException(scriptName, line, functionName); } co.add(new Code(functionName, parCounter.pop(), line)); noFunction = false; break; case ',': parCounter.set(parCounter.size() - 1, parCounter.lastElement() + 1); if(noFunction) { co.add(new Code(Code.convertInput(strings, s.substring(pos + 1, old), true), line)); } old = pos; noFunction = true; break; case '{': layer++; co.add(new Code("goto", 0, layer, line)); break; case '}': co.add(new Code("goto", 0, layer, line)); layer--; break; } pos--; previous = current; } } // --------------------------------------------------------------------- // generating gotos of key words // --------------------------------------------------------------------- Code[] c = co.toArray(new Code[co.size()]); for(int i = 0; i < c.length; i++) { if("goto".equals(c[i].function) && c[i].value != null) { Object value = c[i].value; if((int) value <= 0) { continue; } for(int j = i + 1; j < c.length; j++) { if(value.equals(c[j].value)) { c[i].value = j - i; switch(c[i-1].function) { case "while": c[j].value = whileStart.pollLast() - j - 1; break; /*case "if": case "else": case "try": case "catch": c[j].value = 0; break;*/ default: c[j].value = 0; break; } break; } } } } // end System.out.println("END GENERATE - " + (System.currentTimeMillis() - startTime)); //java.util.Arrays.stream(c).forEach(cod -> System.out.println(cod.toString())); //gotos.forEach((k, v) -> System.out.println("Lable " + k + " " + v)); //System.exit(0); return c; } public static Object convertInput(HashMap map, 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) { System.out.println("Convert-Input - this should never happen"); return ""; } return s.substring(1, s.length() - 1); } else if(variable && s.startsWith("#")) { return convertInput(null, map.get(s), false); } else if(s.equals("true")) { return true; } else if(s.equals("false")) { return false; } else if(s.equals("null")) { return null; } try { if(s.contains("/")) { String[] parts = s.split("/"); return new Fraction(Long.parseLong(parts[0].trim()), Long.parseLong(parts[1].trim())); } return new Fraction(Long.parseLong(s)); } catch(NumberFormatException ex) { try { return Fraction.fromDouble(Double.parseDouble(s)); } catch(NumberFormatException ex2) { if(variable) { return new Variable(s); } return s; } } } public static Object convertInput(String s) { return convertInput(null, s, false); } }