package me.hammerle.code; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import me.hammerle.exceptions.HoldCodeException; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; import me.hammerle.exceptions.FileIOException; import me.hammerle.exceptions.PreScriptException; import me.hammerle.math.Fraction; public class SnuviParser { protected final ISnuviLogger logger; private final ISnuviScheduler scheduler; private final HashMap> functions; public boolean printStack; private int idCounter; private final HashMap scripts; private final ArrayList termQueue; public SnuviParser(ISnuviLogger logger, ISnuviScheduler scheduler) { this.logger = logger; this.scheduler = scheduler; functions = new HashMap<>(); registerStandardLibraries(); printStack = false; scripts = new HashMap<>(); termQueue = new ArrayList<>(); idCounter = 0; } // ----------------------------------------------------------------------------------- // function registry // ----------------------------------------------------------------------------------- public boolean isRegisteredFunction(String s) { return functions.containsKey(s); } public void registerFunction(String s, BiFunction f) { functions.put(s, f); } public void registerConsumer(String s, BiConsumer f) { functions.put(s, (BiFunction) (args, sc) -> { f.accept(args, sc); return Void.TYPE; }); } public void registerAlias(String alias, String original) { BiFunction f = functions.get(original); if(f == null) { throw new IllegalArgumentException("registering alias for non existent function"); } registerFunction(alias, functions.get(original)); } @SuppressWarnings("unchecked") private void registerStandardLibraries() { registerFunction("nothing", (args, sc) -> 0); registerFunction("", (args, sc) -> args[0]); registerConsumer("error", (args, sc) -> printStack = !printStack); // ------------------------------------------------------------------------------- // event // ------------------------------------------------------------------------------- registerConsumer("event.load", (args, sc) -> sc.loadEvent(args[0].toString())); registerConsumer("event.unload", (args, sc) -> sc.unloadEvent(args[0].toString())); registerFunction("event.isloaded", (args, sc) -> sc.isLoadedEvent(args[0].toString())); // ------------------------------------------------------------------------------- // bit // ------------------------------------------------------------------------------- registerFunction("bit.rightshiftn", (args, qd) -> ((Fraction) args[0]).rightShiftNumerator(getInt(args[1]))); registerFunction("bit.rightshiftd", (args, qd) -> ((Fraction) args[0]).rightShiftDenominator(getInt(args[1]))); registerFunction("bit.leftshiftn", (args, qd) -> ((Fraction) args[0]).leftShiftNumerator(getInt(args[1]))); registerFunction("bit.leftshiftd", (args, qd) -> ((Fraction) args[0]).leftShiftDenominator(getInt(args[1]))); registerFunction("bit.and", (args, qd) -> ((Fraction) args[0]).and((Fraction) args[1])); registerFunction("bit.or", (args, qd) -> ((Fraction) args[0]).or((Fraction) args[1])); registerFunction("bit.xor", (args, qd) -> ((Fraction) args[0]).xor((Fraction) args[1])); registerFunction("bit.invert", (args, qd) -> ((Fraction) args[0]).invert()); registerFunction("bit.setn", (args, qd) -> ((Fraction) args[0]).setNumeratorBit(getInt(args[1]))); registerFunction("bit.setd", (args, qd) -> ((Fraction) args[0]).setDenominatorBit(getInt(args[1]))); registerFunction("bit.unsetn", (args, qd) -> ((Fraction) args[0]).unsetNumeratorBit(getInt(args[1]))); registerFunction("bit.unsetd", (args, qd) -> ((Fraction) args[0]).unsetDenominatorBit(getInt(args[1]))); registerFunction("bit.getn", (args, qd) -> ((Fraction) args[0]).getNumeratorBit(getInt(args[1]))); registerFunction("bit.getd", (args, qd) -> ((Fraction) args[0]).getDenominatorBit(getInt(args[1]))); // ------------------------------------------------------------------------------- // math // ------------------------------------------------------------------------------- registerFunction("math.mod", (args, sc) -> new Fraction(getInt(args[0]) % getInt(args[1]))); registerFunction("math.abs", (args, sc) -> ((Fraction) args[0]).abs()); registerFunction("math.pow", (args, sc) -> ((Fraction) args[0]).power((Fraction) args[1])); registerFunction("math.root", (args, sc) -> ((Fraction) args[0]).power(((Fraction) args[1]).invert())); registerFunction("math.sin", (args, sc) -> ((Fraction) args[0]).sin()); registerFunction("math.cos", (args, sc) -> ((Fraction) args[0]).cos()); registerFunction("math.tan", (args, sc) -> ((Fraction) args[0]).tan()); registerFunction("math.asin", (args, sc) -> ((Fraction) args[0]).asin()); registerFunction("math.acos", (args, sc) -> ((Fraction) args[0]).acos()); registerFunction("math.atan", (args, sc) -> ((Fraction) args[0]).atan()); registerFunction("math.e", (args, sc) -> Fraction.E); registerFunction("math.pi", (args, sc) -> Fraction.PI); registerFunction("math.ln", (args, sc) -> ((Fraction) args[0]).log()); registerFunction("math.log", (args, sc) -> ((Fraction) args[0]).log()); registerFunction("math.random", (args, sc) -> new Fraction(ScriptUtils.randomInt(getInt(args[0]), getInt(args[1])))); registerFunction("math.round", (args, sc) -> ((Fraction) args[0]).round()); registerFunction("math.rounddown", (args, sc) -> ((Fraction) args[0]).floor()); registerFunction("math.roundup", (args, sc) -> ((Fraction) args[0]).ceil()); registerFunction("math.roundcomma", (args, sc) -> ((Fraction) args[0]).round(getInt(args[1]))); // ------------------------------------------------------------------------------- // lists // ------------------------------------------------------------------------------- registerConsumer("list.new", (args, sc) -> sc.setVar(args[0].toString(), new ArrayList<>())); registerFunction("list.exists", (args, sc) -> args[0] instanceof List); registerConsumer("list.add", (args, sc) -> ((List) args[0]).add(args[1])); registerConsumer("list.remove", (args, sc) -> ((List) args[0]).remove(args[1])); registerConsumer("list.removeindex", (args, sc) -> ((List) args[0]).remove(getInt(args[1]))); registerFunction("list.contains", (args, sc) -> ((List) args[0]).contains(args[1])); registerFunction("list.getsize", (args, sc) -> new Fraction(((List) args[0]).size())); registerFunction("list.getindex", (args, sc) -> ((List) args[0]).get(getInt(args[1]))); registerConsumer("list.setindex", (args, sc) -> ((List) args[0]).set(getInt(args[1]), args[2])); registerConsumer("list.clear", (args, sc) -> ((List) args[0]).clear()); registerFunction("list.getindexof", (args, sc) -> new Fraction(((List) args[0]).indexOf(args[1]))); registerConsumer("list.sort", (args, sc) -> sortList((List) args[0], sc)); registerConsumer("list.reverse", (args, sc) -> Collections.reverse((List) args[0])); registerConsumer("list.shuffle", (args, sc) -> Collections.shuffle((List) args[0])); // ------------------------------------------------------------------------------- // maps // ------------------------------------------------------------------------------- registerConsumer("map.new", (args, sc) -> sc.setVar(args[0].toString(), new HashMap<>())); registerFunction("map.exists", (args, sc) -> args[0] instanceof Map); registerConsumer("map.add", (args, sc) -> ((HashMap) args[0]).put(args[1], args[2])); registerConsumer("map.remove", (args, sc) -> ((HashMap) args[0]).remove(args[1])); registerFunction("map.contains", (args, sc) -> ((HashMap) args[0]).containsKey(args[1])); registerFunction("map.getsize", (args, sc) -> new Fraction(((HashMap) args[0]).size())); registerFunction("map.get", (args, sc) -> ((HashMap) args[0]).get(args[1])); registerConsumer("map.clear", (args, sc) -> ((HashMap) args[0]).clear()); // ------------------------------------------------------------------------------- // sets // ------------------------------------------------------------------------------- registerConsumer("set.new", (args, sc) -> sc.setVar(args[0].toString(), new HashSet<>())); registerFunction("set.exists", (args, sc) -> args[0] instanceof Set); registerConsumer("set.add", (args, sc) -> ((HashSet) args[0]).add(args[1])); registerConsumer("set.remove", (args, sc) -> ((HashSet) args[0]).remove(args[1])); registerFunction("set.contains", (args, sc) -> ((HashSet) args[0]).contains(args[1])); registerFunction("set.getsize", (args, sc) -> new Fraction(((HashSet) args[0]).size())); registerConsumer("set.tolist", (args, sc) -> sc.setVar(args[0].toString(), ((HashSet) args[1]).stream().collect(Collectors.toList()))); // ------------------------------------------------------------------------------- // time // ------------------------------------------------------------------------------- registerFunction("time.get", (args, sc) -> new Fraction(System.currentTimeMillis())); registerFunction("time.nextday", (args, sc) -> getNextDay(args)); registerFunction("time.getyear", (args, sc) -> getYear(args)); registerFunction("time.getmonth", (args, sc) -> getMonth(args)); registerFunction("time.getday", (args, sc) -> getDay(args)); registerFunction("time.gethour", (args, sc) -> getHour(args)); registerFunction("time.getminute", (args, sc) -> getMinute(args)); registerFunction("time.getsecond", (args, sc) -> getSecond(args)); // ------------------------------------------------------------------------------- // text // ------------------------------------------------------------------------------- registerFunction("text.number", (args, sc) -> fractionToDoubleString((Fraction) args[0])); registerFunction("text.class", (args, sc) -> args[0].getClass().getSimpleName()); registerFunction("text.tolowercase", (args, sc) -> ScriptUtils.connect(args, 0).toLowerCase()); registerAlias("tolowercase", "text.tolowercase"); registerFunction("text.touppercase", (args, sc) -> ScriptUtils.connect(args, 0).toUpperCase()); registerAlias("touppercase", "text.touppercase"); registerConsumer("text.split", (args, sc) -> split(args, sc)); registerAlias("split", "text.split"); registerFunction("text.concatlist", (args, sc) -> ((List) args[0]).stream().limit(getInt(args[3]) + 1).skip(getInt(args[2])).map(o -> o.toString()).collect(Collectors.joining(args[1].toString()))); registerAlias("concatlist", "text.concatlist"); registerFunction("text.concat", (args, sc) -> ScriptUtils.connect(args, 0)); registerAlias("concat", "text.concat"); registerFunction("text", (args, sc) -> String.valueOf(args[0])); registerFunction("text.substring", (args, sc) -> args[0].toString().substring(getInt(args[1]), getInt(args[2]))); registerFunction("text.length", (args, sc) -> args[0].toString().length()); registerFunction("text.startswith", (args, sc) -> args[0].toString().startsWith(args[1].toString(), getInt(args[2]))); registerFunction("text.endswith", (args, sc) -> args[0].toString().endsWith(args[1].toString())); registerFunction("text.contains", (args, sc) -> args[0].toString().contains(args[1].toString())); registerFunction("text.indexof", (args, sc) -> args[0].toString().indexOf(args[1].toString(), getInt(args[2]))); registerFunction("text.lastindexof", (args, sc) -> args[0].toString().lastIndexOf(args[1].toString(), getInt(args[2]))); registerFunction("text.replace", (args, sc) -> args[0].toString().replace(args[1].toString(), args[2].toString())); registerFunction("text.trim", (args, sc) -> args[0].toString().trim()); registerFunction("text.charat", (args, sc) -> String.valueOf(args[0].toString().charAt(getInt(args[1])))); // ------------------------------------------------------------------------------- // files // ------------------------------------------------------------------------------- registerFunction("file.new", (args, sc) -> new File(args[0].toString())); registerFunction("file.exists", (args, sc) -> ((File) args[0]).exists()); registerFunction("file.delete", (args, sc) -> ((File) args[0]).delete()); registerFunction("file.getname", (args, sc) -> ((File) args[0]).getName()); registerFunction("file.", (args, sc) -> ((File) args[0]).renameTo(new File(args[1].toString()))); registerConsumer("file.getlist", (args, sc) -> sc.setVar(args[0].toString(), Arrays.asList(((File) args[0]).listFiles()))); registerConsumer("file.read", (args, sc) -> readFile(args, sc)); registerConsumer("file.write", (args, sc) -> writeFile(args, sc)); // ------------------------------------------------------------------------------- // commands without library // ------------------------------------------------------------------------------- registerFunction("add", (args, sc) -> ((Fraction) args[0]).add((Fraction) args[1])); registerFunction("sub", (args, sc) -> ((Fraction) args[0]).sub((Fraction) args[1])); registerFunction("inc", (args, sc) -> increaseVar(args[0], sc, 1)); registerFunction("dec", (args, sc) -> increaseVar(args[0], sc, -1)); registerFunction("mul", (args, sc) -> ((Fraction) args[0]).mul((Fraction) args[1])); registerFunction("div", (args, sc) -> ((Fraction) args[0]).div((Fraction) args[1])); registerFunction("getvar", (args, sc) -> sc.getVar(args[0].toString())); registerConsumer("setvar", (args, sc) -> sc.setVar(args[0].toString(), args[1])); registerConsumer("removevar", (args, sc) -> sc.removeVar(args[0].toString())); registerConsumer("reset", (args, sc) -> sc.resetLoopCounter()); registerConsumer("wait", (args, sc) -> { sc.resetLoopCounter(); throw new HoldCodeException(); }); registerConsumer("goto", (args, sc) -> goTo(args, sc)); registerConsumer("sgoto", (args, sc) -> scheduleGoto(args, sc)); registerConsumer("gosub", (args, sc) -> sc.gotoLabelWithReturn(args[0].toString())); registerConsumer("return", (args, sc) -> sc.doReturn()); registerConsumer("try", (args, sc) -> {sc.saveTryJumpLine(); sc.jump(1);}); registerConsumer("catch", (args, sc) -> sc.resetTryJumpLine()); registerConsumer("if", (args, sc) -> ifFunction(args, sc)); registerConsumer("else", (args, sc) -> {}); registerConsumer("while", (args, sc) -> whileFunction(args, sc)); registerFunction("equal", (args, sc) -> isEqual(args)); registerAlias("equals", "equal"); registerFunction("less", (args, sc) -> ((Fraction) args[0]).compareTo((Fraction) args[1]) < 0); registerFunction("greater", (args, sc) -> ((Fraction) args[0]).compareTo((Fraction) args[1]) > 0); registerFunction("notequal", (args, sc) -> !isEqual(args)); registerFunction("lessequal", (args, sc) -> ((Fraction) args[0]).compareTo((Fraction) args[1]) <= 0); registerFunction("greaterequal", (args, sc) -> ((Fraction) args[0]).compareTo((Fraction) args[1]) >= 0); registerFunction("invert", (args, sc) -> !((boolean) args[0])); registerFunction("and", (args, sc) -> Arrays.stream(args).allMatch(s -> s.equals(true))); registerFunction("or", (args, sc) -> Arrays.stream(args).anyMatch(s -> s.equals(true))); registerConsumer("waitfor", (args, sc) -> waitFor(args, sc)); registerConsumer("term", (args, sc) -> { termSafe(sc); throw new HoldCodeException(); }); } @SuppressWarnings("unchecked") protected Object parseFunction(Script sc, String function, Object[] args) throws HoldCodeException { try { return functions.get(function).apply(args, sc); } catch(HoldCodeException ex) { throw ex; } catch(Exception ex) { if(printStack) { ex.printStackTrace(); } if(sc.getTryJumpLine() != -1) { sc.setVar("error", ex.getClass().getSimpleName()); sc.gotoTryJumpLine(); sc.resetTryJumpLine(); return Void.TYPE; } logger.printException(ex, sc, sc.getActiveRealCodeLine()); sc.resetLoopCounter(); throw new HoldCodeException(); } } // ----------------------------------------------------------------------------------- // script controller // ----------------------------------------------------------------------------------- public Script getScript(int id) { return scripts.get(id); } public boolean termUnsafe(Script sc) { if(sc == null) { return false; } sc.setInvalid(); sc.onTerm(); return scripts.remove(sc.getId()) != null; } public void termSafe(Script sc) { if(sc == null) { return; } sc.setInvalid(); termQueue.add(sc.getId()); } private void term() { if(!termQueue.isEmpty()) { termQueue.forEach(i -> { Script sc = scripts.remove(i); if(sc != null) { sc.onTerm(); } }); termQueue.clear(); } } public void termAllUnsafe() { scripts.values().forEach(sc -> { sc.onTerm(); sc.setInvalid(); }); scripts.clear(); } public Collection