package me.hammerle.code; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Stack; import java.util.TreeMap; import me.hammerle.exceptions.CodeTooLongException; import me.hammerle.exceptions.GotoLabelNotFoundException; import me.hammerle.exceptions.HoldCodeException; import me.hammerle.exceptions.PreScriptException; import me.hammerle.math.Fraction; public class Script { private final SnuviParser parser; protected final boolean receiveEventBroadcast; protected final ArrayList eventVars; private final int id; private final String name; private final HashMap variables; private final HashMap gotos; private final HashSet events; private final Stack valueStack; private final Stack functionStack; private final Stack returnStack; private final TreeMap forMap; private Code[] code; private int position; private int loopCounter; // halt is only used by waitfor, the script does not react to events if halted private boolean halt; private boolean isRunning; private boolean valid; private int tryJumpLine; public Script(SnuviParser parser, int id, String name, String code, boolean receiveEventBroadcast) { this.eventVars = new ArrayList<>(); this.receiveEventBroadcast = receiveEventBroadcast; this.parser = parser; this.id = id; this.name = name; variables = new HashMap<>(); gotos = new HashMap<>(); events = new HashSet<>(); valueStack = new Stack<>(); functionStack = new Stack<>(); returnStack = new Stack<>(); forMap = new TreeMap<>(); this.code = Code.generate(parser, name, code, gotos); if(this.code.length == 0) { throw new PreScriptException(name, 0, "empty file"); } position = 0; loopCounter = 0; halt = false; isRunning = false; valid = true; tryJumpLine = -1; } public Script(SnuviParser parser, int id, String name, String code) { this(parser, id, name, code, true); } protected void initExpansion(Object... o) { } public void overload(String code) { gotos.clear(); valueStack.clear(); functionStack.clear(); returnStack.clear(); forMap.clear(); this.code = Code.generate(parser, name, code, gotos); position = -1; halt = false; valid = true; tryJumpLine = -1; } public Code[] getCode(int line) { line = code[line].realLine; int start = 0; int end = code.length; int helper; while(end - start > 1) { helper = (start + end) >> 1; if(code[helper].realLine > line) { end = helper; } else if(code[helper].realLine <= line) { start = helper; } } int realEnd = start; start = 0; end = code.length; while(end - start > 1) { helper = (start + end) >> 1; if(code[helper].realLine >= line) { end = helper; } else if(code[helper].realLine < line) { start = helper; } } return Arrays.copyOfRange(code, end, realEnd + 1); } // ----------------------------------------------------------------------------------- // Script-Daten // ----------------------------------------------------------------------------------- public ISnuviLogger getLogger() { return parser.logger; } public int getId() { return id; } public String getName() { return name; } public boolean isHalt() { return halt; } public void setHalt(boolean b) { halt = b; } public boolean isRunning() { return isRunning; } public boolean isValid() { return valid; } public void setInvalid() { valid = false; } public int getActiveRealCodeLine() { if(position < 0 || position >= code.length) { return 0; } return code[position].realLine; } // ----------------------------------------------------------------------------------- // Event-Handling // ----------------------------------------------------------------------------------- public void loadEvent(String s) { events.add(s); } public boolean isLoadedEvent(String s) { return events.contains(s); } public void unloadEvent(String s) { events.remove(s); } // ----------------------------------------------------------------------------------- // Script-Flow // ----------------------------------------------------------------------------------- public void onTerm() { } public void runCode() { if(this.isValid()) { try { isRunning = true; while(position < code.length) { code[position].executeFunction(parser, this, valueStack); position++; } isRunning = false; parser.termSafe(this); } catch(Exception ex) { isRunning = false; if(ex.getClass() != HoldCodeException.class) { parser.logger.printException(ex, code[position].function, this, getActiveRealCodeLine()); } position++; } } } // ----------------------------------------------------------------------------------- // Variablen // ----------------------------------------------------------------------------------- public void setEventVar(String var, Object value) { variables.put(var, value); eventVars.add(var); } public void setVar(String var, Object value) { variables.put(var, value); } public Object getVar(String var) { return variables.get(var); } public HashMap getVars() { return variables; } public boolean getBooleanVar(String var) { try { return (boolean) getVar(var); } catch(ClassCastException | NullPointerException ex) { return false; } } public void removeVar(String var) { variables.remove(var); } // ----------------------------------------------------------------------------------- // Goto, Return // ----------------------------------------------------------------------------------- public void incLoopCounter() { loopCounter++; if(loopCounter > 50) { resetLoopCounter(); throw new CodeTooLongException(); } } public void gotoLabel(String label, boolean scheduled, boolean resetFor) { Integer i = gotos.get(label); if(i == null) { throw new GotoLabelNotFoundException(label); } if(resetFor) { forMap.clear(); } incLoopCounter(); position = i - (scheduled ? 0 : 1); } public void resetLoopCounter() { loopCounter = 0; } public void gotoLabelWithReturn(String label) { returnStack.push(position); gotoLabel(label, false, false); } public void doReturn() { position = returnStack.pop(); } public void jump() { position += (int) code[position].value; } public void jumpDoElse() { position += (int) code[position].value; if(code.length <= position + 1) { return; } if("else".equals(code[position + 1].function)) { position++; } } // ----------------------------------------------------------------------------------- // try // ----------------------------------------------------------------------------------- public void gotoTryJumpLine() { position = tryJumpLine; } public void saveTryJumpLine() { tryJumpLine = position + ((int) code[position].value) + 1; if(!"catch".equals(code[tryJumpLine].function)) { throw new IllegalStateException("try without catch"); } } public void resetTryJumpLine() { tryJumpLine = -1; } public int getTryJumpLine() { return tryJumpLine; } // ------------------------------------------------------------------------- // for // ------------------------------------------------------------------------- public boolean repeatFinished(Fraction value, Fraction step, Fraction border) { return (step.isNegative() && border.compareTo(value) > 0) || border.compareTo(value) < 0; } public Fraction nextRepeatData(Fraction start, Fraction step) { Fraction f = forMap.get(position); if(f == null) { forMap.put(position, start); return start; } f = f.add(step); forMap.put(position, f); return f; } public void clearRepeatData() { forMap.remove(position); } public void clearLastRepeatData() { forMap.pollLastEntry(); } // ------------------------------------------------------------------------- // push, pop stuff for functions // ------------------------------------------------------------------------- public void pushFunctionInput(Object[] o) { for(Object ob : o) { valueStack.push(ob); } } public void pushFunctionInput(Object[] o, Fraction f) { pushFunctionInput(o); valueStack.push(f); } public Object popFunctionInput() { return valueStack.pop(); } public void push(int start, Object... o) { for(int i = start; i < o.length; i++) { functionStack.push(o[i]); } } public void pop(Object... o) { for(int i = o.length - 1; i >= 0; i--) { setVar(o[i].toString(), functionStack.pop()); } } }