package me.hammerle.code; import java.util.ArrayList; import java.util.HashMap; import java.util.Stack; import me.hammerle.exceptions.PreScriptException; import me.hammerle.math.Fraction; public class LineCompiler { private int realLine; // comma counter, for functions private final Stack commaCounter; private String line; private byte layer; private String scriptName; // helper to support things like print(-3); private boolean minus; public LineCompiler(SnuviParser parser, String scriptName) { realLine = 0; commaCounter = new Stack<>(); line = ""; layer = 0; minus = false; this.scriptName = scriptName; } public void reset(int realLine, byte layer, String line) { this.realLine = realLine; commaCounter.clear(); minus = false; this.line = line; this.layer = layer; } private boolean isAllowedChar(char c) { return Character.isLetterOrDigit(c) || c == '.' || c == '_' || c == '#' || c == '$'; } public void compile(ArrayList co, HashMap strings) { Stack> syntaxStack = new Stack<>(); syntaxStack.push(new Stack<>()); char[] chars = line.toCharArray(); char c; int old = chars.length; Syntax syntax; String s; for(int i = old - 1; i >= 0; i--) { c = chars[i]; if(!isAllowedChar(c)) { syntax = getSyntax(line, i); if(syntax == Syntax.UNKNOWN) { throw new PreScriptException(scriptName, realLine, "unexpected character '" + c + "'"); } else if(syntax.isIncOrDec()) { Code change = co.get(co.size() - 1); if(change.function.equals("array.get")) { change.function = "array." + syntax.getFunction(); } } s = line.substring(i + 1, old).toLowerCase(); if(!s.isEmpty()) { Stack stack = syntaxStack.peek(); if(!stack.isEmpty()) { Syntax sy = stack.peek(); if(sy == Syntax.INC) { stack.pop(); stack.push(Syntax.POST_INC); } else if(sy == Syntax.DEC) { stack.pop(); stack.push(Syntax.POST_DEC); } } co.add(new Code(Code.convertInput(strings, s, true), realLine, layer)); minus = false; } i += 1 - syntax.getFunction().length(); old = i; if(syntax.shouldStartLayer()) { syntaxStack.push(new Stack<>()); commaCounter.push(0); continue; } else if(syntax.shouldEndLayer()) { Stack currentStack = syntaxStack.pop(); while(!currentStack.isEmpty()) { doStackEmptying(co, currentStack.pop()); } old--; int pos = old; while(old >= 0 && isAllowedChar(chars[old])) { old--; } old++; s = line.substring(old, pos + 1).toLowerCase(); if(syntax.isArray()) { minus = false; co.add(new Code(s, realLine, layer)); if(!syntaxStack.isEmpty()) { Stack arrayFunction = syntaxStack.pop(); if(!arrayFunction.isEmpty()) { Syntax sy = arrayFunction.pop(); if(sy.isIncOrDec()) { if(sy == Syntax.INC) { sy = Syntax.POST_INC; } else if(sy == Syntax.DEC) { sy = Syntax.POST_DEC; } } co.add(new Code("array." + sy.getFunction(), sy.isIncOrDec() ? 2 : 3, realLine, layer)); continue; } } co.add(new Code("array.get", 2, realLine, layer)); } else { minus = false; if(line.startsWith("()", pos + 1)) { co.add(new Code(s, 0, realLine, layer)); } else { co.add(new Code(s, commaCounter.pop() + 1, realLine, layer)); } } continue; } else if(!syntaxStack.isEmpty()) { Stack currentStack = syntaxStack.peek(); while(!currentStack.isEmpty() && currentStack.peek().getWeight() <= syntax.getWeight()) { doStackEmptying(co, currentStack.pop()); } currentStack.push(syntax); } if(syntax == Syntax.COMMA) { commaCounter.push(commaCounter.pop() + 1); } else if(syntax == Syntax.SUB) { minus = true; } } } s = line.substring(0, old).toLowerCase(); if(!s.isEmpty()) { co.add(new Code(s, realLine, layer)); } if(!syntaxStack.isEmpty()) { Stack currentStack = syntaxStack.peek(); while(!currentStack.isEmpty()) { doStackEmptying(co, currentStack.pop()); } } } private void doStackEmptying(ArrayList co, Syntax sy) { if(sy.isFunction()) { if(sy.isIncOrDec()) { Code change = co.get(co.size() - 1); if(change.value instanceof Variable) { change.value = ((Variable) change.value).getName(); } co.add(new Code(sy.getFunction(), sy.getParameters(), realLine, layer)); } else { if(minus) { minus = false; co.add(new Code(new Fraction(0), realLine, layer)); } co.add(new Code(sy.getFunction(), sy.getParameters(), realLine, layer)); } } } private static Syntax getSyntax(String code, int pos) { // priorities from C specification // http://en.cppreference.com/w/c/language/operator_precedence switch(code.charAt(pos)) { case '(': return Syntax.OPEN_BRACKET; case ')': return Syntax.CLOSE_BRACKET; case '[': return Syntax.OPEN_SBRACKET; case ']': return Syntax.CLOSE_SBRACKET; case '!': return Syntax.NOT; case '~': return Syntax.BIT_NOT; case '*': return Syntax.MUL; case '/': return Syntax.DIV; case '%': return Syntax.MOD; case '^': return Syntax.BIT_XOR; case ',': return Syntax.COMMA; case '+': if(pos >= 1 && code.charAt(pos - 1) == '+') { return Syntax.INC; } return Syntax.ADD; case '-': if(pos >= 1 && code.charAt(pos - 1) == '-') { return Syntax.DEC; } return Syntax.SUB; case '<': if(pos >= 1 && code.charAt(pos - 1) == '<') { return Syntax.LEFT_SHIFT; } return Syntax.SMALLER; case '>': if(pos >= 1 && code.charAt(pos - 1) == '>') { return Syntax.RIGHT_SHIFT; } return Syntax.GREATER; case '&': if(pos >= 1 && code.charAt(pos - 1) == '&') { return Syntax.AND; } return Syntax.BIT_AND; case '|': if(pos >= 1 && code.charAt(pos - 1) == '|') { return Syntax.OR; } return Syntax.BIT_OR; case '=': if(pos >= 1) { switch(code.charAt(pos - 1)) { case '<': if(pos >= 2 && code.charAt(pos - 2) == '<') { return Syntax.SET_SHIFT_LEFT; } return Syntax.SMALLER_EQUAL; case '>': if(pos >= 2 && code.charAt(pos - 2) == '>') { return Syntax.SET_SHIFT_RIGHT; } return Syntax.GREATER_EQUAL; case '=': return Syntax.EQUAL; case '!': return Syntax.NOT_EQUAL; case '+': return Syntax.SET_ADD; case '-': return Syntax.SET_SUB; case '*': return Syntax.SET_MUL; case '/': return Syntax.SET_DIV; case '%': return Syntax.SET_MOD; case '&': return Syntax.SET_BIT_AND; case '^': return Syntax.SET_BIT_XOR; case '|': return Syntax.SET_BIT_OR; } } return Syntax.SET; } return Syntax.UNKNOWN; } }