Browse Source

try, catch, sgoto, waitfor, term, SnuviParser, auto scheduling for too long code

Kajetan Johannes Hammerle 7 years ago
parent
commit
efa18b7acb

+ 58 - 3
src/me/hammerle/snuviscript/SnuviScript.java

@@ -4,14 +4,69 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import me.hammerle.snuviscript.code.ISnuviLogger;
+import me.hammerle.snuviscript.code.ISnuviScheduler;
 import me.hammerle.snuviscript.code.Script;
+import me.hammerle.snuviscript.code.SnuviParser;
 
 public class SnuviScript
 {
     public static void main(String[] args) throws IOException 
     {
-        List<String> lines = Files.readAllLines(new File("./test.sbasic").toPath());
-        Script sc = new Script(null, lines, "test");
-        System.out.println("\n" + sc.run());
+        ISnuviLogger logger = new ISnuviLogger() 
+        {
+            @Override
+            public void print(String message, Exception ex, String function, String scriptname, Script sc, int line) 
+            {
+                System.out.println("________________________________________");
+                System.out.println("Exception");
+                if(message != null)
+                {
+                    System.out.println(" - " + message);
+                }
+                if(ex != null)
+                {
+                    System.out.println(" - " + ex.getClass().getSimpleName());
+                }
+                if(function != null)
+                {
+                    System.out.println(" - Funktion: " + function);
+                }
+                if(scriptname != null)
+                {
+                    System.out.println(" - Script: " + scriptname);
+                }
+                if(line != -1)
+                {
+                    System.out.println(" - Line: " + line);
+                }
+            }
+        };
+        ISnuviScheduler scheduler = new ISnuviScheduler() 
+        {
+            @Override
+            public int scheduleTask(Runnable r) 
+            {
+                System.out.println("SCHEDULER");
+                return 0;
+            }
+
+            @Override
+            public int scheduleTask(Runnable r, long delay) 
+            {
+                ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();  
+                executor.schedule(r, delay, TimeUnit.MILLISECONDS);
+                executor.shutdown();
+                return 1;
+            }
+        };
+        SnuviParser parser = new SnuviParser(logger, scheduler);
+        parser.startScript("./test", ".sbasic", true);
+        parser.startScript("./test", ".sbasic", true);
+        
+        parser.callEvent("testevent", null, null);
     }  
 }

+ 1 - 0
src/me/hammerle/snuviscript/code/BasicFunction.java

@@ -20,6 +20,7 @@ public final class BasicFunction
     
     public Object execute(Script sc, InputProvider[] input)
     {
+        sc.currentFunction = name;
         return f.apply(sc, input);
     }
 }

+ 95 - 24
src/me/hammerle/snuviscript/code/Compiler.java

@@ -38,6 +38,8 @@ public class Compiler
     private int lineOffset;
     private int layer;
     
+    private JumpData tryState;
+    
     private class JumpWrapper
     {
         private final JumpData data;
@@ -71,6 +73,7 @@ public class Compiler
         this.code = new LinkedList<>();
         this.line = 0;
         this.layer = 0;
+        this.tryState = null;
         this.jumps = new Stack<>();
         this.loopJumps = new Stack<>();
         this.breakContinueJumps = new LinkedList<>();
@@ -81,13 +84,21 @@ public class Compiler
     
     private void addCodeInstruction(String function, InputProvider[] input)
     {
-        code.add(new Instruction(line + lineOffset, (byte) layer, FunctionLoader.getFunction(function), input));
+        code.add(new Instruction(line + lineOffset + 1, (byte) layer, FunctionLoader.getFunction(function), input));
+    }
+    
+    private void addLabel(String name, int line)
+    {
+        if(labels.put(name, line) != null)
+        {
+            throw new PreScriptException("label duplicate", line);
+        }
     }
     
     private Instruction[] compile()
     {
         int size = sCode.size();
-        System.out.println("__________________________________");
+        //System.out.println("__________________________________");
         
         StringBuilder sb = new StringBuilder();
         String replacement;
@@ -96,6 +107,7 @@ public class Compiler
         int old = 0;
         boolean text = false;
         boolean comment = false;
+        int labelIndex;
         
         for(line = 0; line < size; line++)
         {
@@ -156,6 +168,18 @@ public class Compiler
                         JumpWrapper data = jumps.pop();
                         switch(data.function)
                         {
+                            case "try":
+                            {
+                                tryState = data.data;
+                                break;
+                            }
+                            case "catch":
+                            {
+                                data.data.setRelativeJump(code.size());
+                                break;
+                            }
+                            case "else":
+                            case "elseif":
                             case "if":
                             {
                                 data.data.setRelativeJump(code.size());
@@ -288,34 +312,65 @@ public class Compiler
                 }
                 pos++;
             }
+            labelIndex = sb.indexOf("@");
+            if(labelIndex != -1)
+            {
+                String label = sb.toString().trim();
+                if(label.charAt(0) != '@')
+                {
+                    throw new PreScriptException("you seriously fucked up the syntax here", line);
+                }
+                addLabel(label.substring(1), code.size() - 1);
+                sb = new StringBuilder();
+            }
         }
         
-        System.out.println("__________________________________");
+        //System.out.println("__________________________________");
         
         Instruction[] input = code.toArray(new Instruction[code.size()]);
         
-        for(Instruction in : input)
+        /*for(Instruction in : input)
         {
             System.out.println(in);
-        }
-        System.out.println("__________________________________");
-        labels.entrySet().stream().forEach((e) -> 
+        }*/
+        //System.out.println("__________________________________");
+        /*labels.entrySet().stream().forEach((e) -> 
         {
             System.out.println("LABEL " + e.getKey() + " " + e.getValue());
-        });
-        System.out.println("__________________________________");
+        });*/
+        //System.out.println("__________________________________");
         return input;
     }
     
     private void compileLine(String currentCode)
     {
-        if(currentCode.startsWith("'"))
-        {
-            return;
-        }
         //System.out.println(">>>"  + currentCode);
         String[] parts = Utils.split(strings, currentCode, line);
         //System.out.println(">>> " + String.join("_", parts));
+        if(tryState != null)
+        {
+            switch(parts.length)
+            {
+                case 0: return;
+                case 1: 
+                    if(!parts[0].equals("catch"))
+                    {
+                        throw new PreScriptException("no catch after try", line);
+                    }
+                    if(tryState == null)
+                    {
+                        throw new PreScriptException("catch without try", line);
+                    }
+                    tryState.setRelativeJump(code.size());
+                    JumpData jump = new JumpData(code.size());
+                    addCodeInstruction("catch", new InputProvider[] {jump});
+                    jumps.push(new JumpWrapper(jump, "catch"));
+                    tryState = null;
+                    return;
+                default:
+                    throw new PreScriptException("invalid catch after try", line);
+            }
+        }
         
         if(parts.length == 0)
         {
@@ -332,7 +387,7 @@ public class Compiler
             {
                 throw new PreScriptException("arguments after label", line);
             }
-            labels.put(parts[0].substring(1), code.size() - 1);
+            addLabel(parts[0].substring(1), code.size() - 1);
             return;
         }
         
@@ -349,17 +404,33 @@ public class Compiler
             {
                 switch(parts[0])
                 {
+                    case "try":
+                    {
+                        JumpData jump = new JumpData(code.size());
+                        addCodeInstruction("try", new InputProvider[] {jump});
+                        jumps.push(new JumpWrapper(jump, "try"));
+                        return;
+                    }
+                    case "else":
+                    {
+                        JumpData jump = new JumpData(code.size());
+                        addCodeInstruction("else", new InputProvider[] {jump});
+                        jumps.push(new JumpWrapper(jump, "else"));
+                        return;
+                    }
                     case "while":
                         throw new PreScriptException("missing syntax at while", line);
                     case "if":
                         throw new PreScriptException("missing syntax at if", line);
+                    case "elseif":
+                        throw new PreScriptException("missing syntax at elseif", line);
                     case "for":
                         throw new PreScriptException("missing syntax at for", line);             
                     case "break":
                     {
                         if(loopJumps.isEmpty())
                         {
-                            throw new IllegalStateException("break without a loop");
+                            throw new PreScriptException("break without a loop", line);
                         }
                         JumpData jump = new JumpData(code.size() - 1);
                         breakContinueJumps.add(jump);
@@ -370,7 +441,7 @@ public class Compiler
                     {
                         if(loopJumps.isEmpty())
                         {
-                            throw new IllegalStateException("continue without a loop");
+                            throw new PreScriptException("continue without a loop", line);
                         }
                         JumpData jump = new JumpData(code.size());
                         breakContinueJumps.add(jump);
@@ -398,7 +469,6 @@ public class Compiler
                 case "--":
                     input = parts[1];
                     parts = new String[] {parts[0]};
-                    //System.out.println(String.join("__", parts));
                     break;
                 case "=":
                 case "+=":
@@ -431,8 +501,11 @@ public class Compiler
         
         switch(input)
         {
+            case "elseif":
+                createIf("elseif", parts);
+                break;
             case "if":
-                createIf(parts);
+                createIf("if", parts);
                 break;
             case "for":
                 createFor(parts);
@@ -512,7 +585,6 @@ public class Compiler
                         default:
                             throw new PreScriptException("missing syntax argument", line);
                     }
-                    System.out.println(syntax);
                 }
                 // pushing weaker functions
                 int weight = sy.getWeight();
@@ -664,7 +736,7 @@ public class Compiler
         }
     }
     
-    private void createIf(String[] parts)
+    private void createIf(String name, String[] parts)
     {
         InputProvider[] input = compileFunction(parts, false);
         InputProvider[] realInput = new InputProvider[input.length + 1];
@@ -672,9 +744,9 @@ public class Compiler
         System.arraycopy(input, 0, realInput, 0, input.length);
         JumpData jump = new JumpData(code.size());
         realInput[input.length] = jump;
-        jumps.push(new JumpWrapper(jump, "if"));
+        jumps.push(new JumpWrapper(jump, name));
         
-        addCodeInstruction("if", realInput);
+        addCodeInstruction(name, realInput);
     }
     
     private void createFor(String[] parts)
@@ -732,5 +804,4 @@ public class Compiler
         breakContinueJumps.forEach(jump -> jump.setRelativeJump(current));
         breakContinueJumps.clear();
     }
-}
-// 811
+}

+ 7 - 1
src/me/hammerle/snuviscript/code/Function.java

@@ -8,7 +8,7 @@ import me.hammerle.snuviscript.variable.LocalArrayVariable;
 public class Function extends InputProvider
 {
     private final BasicFunction function;
-    private InputProvider[] input;
+    private final InputProvider[] input;
     
     public Function(BasicFunction function, InputProvider[] input)
     {
@@ -75,6 +75,12 @@ public class Function extends InputProvider
         return String.valueOf(function.execute(sc, input));
     }
     
+    @Override
+    public boolean getBoolean(Script sc)
+    {
+        return (Boolean) function.execute(sc, input);
+    }
+    
     @Override
     public Variable getVariable(Script sc)
     {

+ 69 - 16
src/me/hammerle/snuviscript/code/FunctionLoader.java

@@ -32,22 +32,22 @@ public class FunctionLoader
 {
     private static final HashMap<String, BasicFunction> FUNCTIONS = new HashMap<>();
     
-    public static void registerFunction(String name, String fname, BiFunction<Script, InputProvider[], Object> f)
+    protected static void registerFunction(String name, String fname, BiFunction<Script, InputProvider[], Object> f)
     {
         FUNCTIONS.put(name, new BasicFunction(fname, f));
     }
     
-    public static void registerFunction(String name, BiFunction<Script, InputProvider[], Object> f)
+    protected static void registerFunction(String name, BiFunction<Script, InputProvider[], Object> f)
     {
         registerFunction(name, name, f);
     }
     
-    public static void registerAlias(String original, String alias)
+    protected static void registerAlias(String original, String alias)
     {
         FUNCTIONS.put(alias, FUNCTIONS.get(original));
     }
     
-    public static BasicFunction getFunction(String f)
+    protected static BasicFunction getFunction(String f)
     {
         final String function = f.toLowerCase();
         return FUNCTIONS.getOrDefault(function, new BasicFunction(function, (sc, in) -> 
@@ -115,12 +115,12 @@ public class FunctionLoader
         // --------------------------------------------------------------------- 
         registerFunction("event.load", (sc, in) ->
         {
-            sc.loadEvent(in[0].getString(sc)); 
+            sc.events.add(in[0].getString(sc));
             return Void.TYPE;
         });
         registerFunction("event.unload", (sc, in) ->
         {
-            sc.unloadEvent(in[0].getString(sc)); 
+            sc.events.remove(in[0].getString(sc));
             return Void.TYPE;
         });
         registerFunction("event.isloaded", (sc, in) -> sc.isEventLoaded(in[0].getString(sc)));
@@ -581,19 +581,22 @@ public class FunctionLoader
         registerFunction("getvar", (sc, in) -> sc.getVar(in[0].getString(sc)).get(sc));      
         registerFunction("wait", (sc, in) -> 
         {
-            sc.setWaiting(true);
+            sc.isWaiting = true;
             return Void.TYPE;
         });
         
         // try - catch
         registerFunction("try", (sc, in) -> 
         {
-            // TODO
+            sc.catchLine = sc.currentLine + in[0].getInt(sc);
             return Void.TYPE;
         });              
         registerFunction("catch", (sc, in) -> 
         {
-            // TODO
+            if(sc.catchLine != -1)
+            {
+                sc.currentLine += in[0].getInt(sc);
+            }
             return Void.TYPE;
         });  
         
@@ -602,10 +605,24 @@ public class FunctionLoader
         {
             sc.currentLine = sc.labels.get(in[0].getString(sc));
             return Void.TYPE;
-        });
+        });       
         registerFunction("sgoto", (sc, in) -> 
         {
-            // TODO
+            int time = in[0].getInt(sc);
+            if(time < 0)
+            {
+                throw new IllegalArgumentException("time units can't be negative");
+            }
+            int label = sc.labels.get(in[1].getString(sc));
+            sc.scheduler.scheduleTask(() -> 
+            {
+                if(!sc.isValid || sc.isHolded)
+                {
+                    return;
+                }
+                sc.currentLine = label + 1;
+                sc.run();
+            }, time);
             return Void.TYPE;
         });
         registerFunction("gosub", (sc, in) -> 
@@ -629,21 +646,41 @@ public class FunctionLoader
         });
         registerFunction("if", (sc, in) -> 
         {
-            int p = in[0].getInt(sc);
-            if(p == 0)
+            sc.ifState = in[0].getBoolean(sc);
+            if(!sc.ifState)
             {
                 sc.currentLine += in[1].getInt(sc);
             }
             return Void.TYPE;
         });
+        registerFunction("elseif", (sc, in) -> 
+        {
+            if(sc.ifState)
+            {
+                sc.currentLine += in[1].getInt(sc);
+            }
+            else
+            {
+                sc.ifState = in[0].getBoolean(sc);
+                if(!sc.ifState)
+                {
+                    sc.currentLine += in[1].getInt(sc);
+                }
+            }
+            return Void.TYPE;
+        });
         registerFunction("else", (sc, in) -> 
         {
-            // TODO
+            if(sc.ifState)
+            {
+                sc.currentLine += in[0].getInt(sc);
+            }
+            sc.ifState = true;
             return Void.TYPE;
         });  
         registerFunction("while", (sc, in) -> 
         {
-            if(in[0].getInt(sc) == 0)
+            if(!in[0].getBoolean(sc))
             {
                 sc.currentLine += in[1].getInt(sc);
             }
@@ -728,7 +765,23 @@ public class FunctionLoader
         
         registerFunction("waitfor", (sc, in) ->    
         {
-            // TODO
+            long l = in[0].getInt(sc);
+            if(l < 0)
+            {
+                throw new IllegalArgumentException("time units can't be negative");
+            }
+            sc.isHolded = true;
+            sc.scheduler.scheduleTask(() -> 
+            {           
+                // activate this again on NullPointerException
+                // if(sc == null || !sc.isValid)
+                if(sc.isValid)
+                {
+                    sc.isHolded = false;
+                    sc.run();
+                }
+            }, l); 
+            sc.isWaiting = true;
             return Void.TYPE;
         });
         registerFunction("term", (sc, in) -> 

+ 6 - 1
src/me/hammerle/snuviscript/code/Instruction.java

@@ -6,7 +6,7 @@ public class Instruction
     private final byte layer;
     
     private final BasicFunction function;
-    private InputProvider[] input;
+    private final InputProvider[] input;
     
     public Instruction(int realLine, byte layer, BasicFunction function, InputProvider[] input)
     {
@@ -20,6 +20,11 @@ public class Instruction
     {
         return layer;
     }
+    
+    public int getRealLine() 
+    {
+        return realLine;
+    }
 
     public InputProvider[] getParameters() 
     {

+ 137 - 17
src/me/hammerle/snuviscript/code/Script.java

@@ -4,21 +4,39 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Stack;
+import me.hammerle.snuviscript.variable.LocalVariable;
 import me.hammerle.snuviscript.variable.Variable;
 
 public final class Script 
 {
+    protected final String name;
+    protected final int id;
+    
     protected ISnuviLogger logger;
+    protected ISnuviScheduler scheduler;
     
     protected int currentLine;
     protected Instruction[] code;
+    // waiting scripts stop executing and run again on an event
     protected boolean isWaiting;
+    // holded scripts do not receive events
+    protected boolean isHolded;
+    // not valid means the script is waiting for its termination
+    protected boolean isValid;
+    // states if event broadcasts should be received, otherwise only direct event calls work
+    protected boolean receiveEventBroadcast;
+    // stores the used cpuTime, schedules the script if too high
+    protected long cpuTime;
+    
+    protected int catchLine;
+    protected String currentFunction;
+    protected boolean ifState;
     
     protected final HashMap<String, Integer> labels;
     protected final Stack<Integer> returnStack;
     protected HashMap<String, Variable> vars;
     protected final Stack<HashMap<String, Variable>> localVars;
-    private final HashSet<String> events;
+    protected final HashSet<String> events;
     
     protected Object returnValue;
     protected final boolean subScript;
@@ -27,11 +45,14 @@ public final class Script
     
     protected boolean printStackTrace;
     
-    protected final String name;
+    private final Runnable onStart;
+    private final Runnable onTerm;
     
-    public Script(ISnuviLogger logger, List<String> code, String name)
+    public Script(ISnuviLogger logger, ISnuviScheduler scheduler, List<String> code, String name, 
+            int id, Runnable onStart, Runnable onTerm, boolean receiveEventBroadcast)
     {
         this.logger = logger;
+        this.scheduler = scheduler;
         this.subScriptInput = null;
         this.subScripts = new HashMap<>();
         this.labels = new HashMap<>();
@@ -41,8 +62,18 @@ public final class Script
         this.subScript = false;
         this.currentLine = 0;
         this.isWaiting = false;
+        this.isHolded = false;
+        this.isValid = true;
+        this.receiveEventBroadcast = receiveEventBroadcast;
+        this.cpuTime = 0;
+        this.catchLine = -1;
+        this.currentFunction = null;
+        this.ifState = true;
         this.printStackTrace = false;
         this.name = name;
+        this.id = id;
+        this.onStart = onStart;
+        this.onTerm = onTerm;
         
         this.code = Compiler.compile(this, code, labels, subScript, 0);
     }
@@ -50,6 +81,7 @@ public final class Script
     public Script(List<String> code, String[] subScriptInput, Script sc, int lineOffset)
     {
         this.logger = sc.logger;
+        this.scheduler = sc.scheduler;
         this.subScriptInput = subScriptInput;
         this.subScripts = sc.subScripts;
         this.labels = new HashMap<>();
@@ -59,8 +91,15 @@ public final class Script
         this.subScript = true;
         this.currentLine = 0;
         this.isWaiting = sc.isWaiting;
+        this.isHolded = sc.isHolded;
+        this.isValid = sc.isValid;
+        this.receiveEventBroadcast = sc.receiveEventBroadcast;
+        this.catchLine = -1;
         this.printStackTrace = false;
         this.name = sc.name;
+        this.id = sc.id;
+        this.onStart = sc.onStart;
+        this.onTerm = sc.onTerm;
         
         this.code = Compiler.compile(this, code, labels, subScript, lineOffset); 
     }
@@ -76,13 +115,57 @@ public final class Script
     
     public Object run()
     {
+        if(isHolded)
+        {
+            return returnValue;
+        }
         int length = code.length;
         returnValue = null;
+        isWaiting = false;
+        cpuTime = 0;
+        long time;
         while(currentLine < length && !isWaiting)
         {
-            //System.out.println("EXECUTE: " + code[currentLine]);
-            code[currentLine].execute(this);
-            currentLine++;
+            time = System.nanoTime();
+            try
+            {
+                //System.out.println("EXECUTE: " + code[currentLine]);
+                code[currentLine].execute(this);
+                currentLine++;
+            }
+            catch(Exception ex)
+            {
+                if(printStackTrace)
+                {
+                    ex.printStackTrace();
+                }
+                if(catchLine != -1)
+                {
+                    currentLine = catchLine + 1; // + 1 because currentLine++ isn't happening
+                    catchLine = -1;
+                    setVar("error", ex.getClass().getSimpleName());
+                    continue;
+                }
+                logger.print(ex.getLocalizedMessage(), ex, currentFunction, name, this, code[currentLine].getRealLine() + 1);
+                ex.printStackTrace();
+                return returnValue;
+            }
+            time = System.nanoTime() - time;
+            cpuTime += time;
+            if(cpuTime > 10_000_000)
+            {
+                isWaiting = true;
+                isHolded = true;
+                scheduler.scheduleTask(() -> 
+                {           
+                    if(isValid)
+                    {
+                        isHolded = false;
+                        run();
+                    }
+                }, 1);
+                return Void.TYPE;
+            }
         }
         return returnValue;
     }
@@ -92,11 +175,6 @@ public final class Script
         currentLine = code.length;
     }
     
-    public void setWaiting(boolean isWaiting)
-    {
-        this.isWaiting = isWaiting;
-    }
-    
     // -------------------------------------------------------------------------
     // general stuff
     // -------------------------------------------------------------------------
@@ -105,6 +183,11 @@ public final class Script
     {
         return name;
     }
+    
+    public int getId() 
+    {
+        return id;
+    }
 
     public ISnuviLogger getLogger()
     {
@@ -124,23 +207,60 @@ public final class Script
         }
         return map.get(name);
     }
+    
+    public void setVar(String name, Object value)
+    {
+        HashMap<String, Variable> map;
+        if(subScript)
+        {
+            map = localVars.peek();
+            Variable var = map.get(name);
+            if(var == null)
+            {
+                var = new LocalVariable(name);
+                map.put(name, var);
+            }
+            var.set(this, value);
+        }
+        else
+        {
+            map = vars;
+            Variable var = map.get(name);
+            if(var == null)
+            {
+                var = new Variable(name);
+                map.put(name, var);
+            }
+            var.set(this, value);
+        }
+    }
 
     // -------------------------------------------------------------------------
     // event handling
     // -------------------------------------------------------------------------
     
-    public void loadEvent(String s)
+    public boolean isEventLoaded(String s)
     {
-        events.add(s);
+        return events.contains(s);
     }
     
-    public boolean isEventLoaded(String s)
+    // -------------------------------------------------------------------------
+    // onStart onTerm
+    // -------------------------------------------------------------------------
+    
+    public void onStart()
     {
-        return events.contains(s);
+        if(onStart != null)
+        {
+            onStart.run();
+        }
     }
     
-    public void unloadEvent(String s)
+    public void onTerm()
     {
-        events.remove(s);
+        if(onTerm != null)
+        {
+            onTerm.run();
+        }
     }
 }

+ 186 - 0
src/me/hammerle/snuviscript/code/SnuviParser.java

@@ -0,0 +1,186 @@
+package me.hammerle.snuviscript.code;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import me.hammerle.snuviscript.exceptions.PreScriptException;
+
+public class SnuviParser 
+{
+    private final ISnuviLogger logger;
+    private final ISnuviScheduler scheduler;
+    
+    private int idCounter;
+    private final HashMap<Integer, Script> scripts;
+    private final LinkedList<Integer> termQueue;
+    
+    public SnuviParser(ISnuviLogger logger, ISnuviScheduler scheduler)
+    {
+        this.logger = logger;
+        this.scheduler = scheduler;
+        
+        scripts = new HashMap<>();
+        termQueue = new LinkedList<>();
+        idCounter = 0;
+    }
+    
+    // -----------------------------------------------------------------------------------
+    // function registry
+    // -----------------------------------------------------------------------------------
+
+    public void registerFunction(String s, BiFunction<Script, InputProvider[], Object> f)
+    {
+        FunctionLoader.registerFunction(s, f);
+    }
+    
+    public void registerAlias(String original, String alias)
+    {
+        FunctionLoader.registerAlias(original, alias);
+    }
+    
+    // -----------------------------------------------------------------------------------
+    // script controller
+    // -----------------------------------------------------------------------------------
+    
+    public Script getScript(int id)
+    {
+        return scripts.get(id);
+    }
+    
+    public boolean termUnsafe(Script sc)
+    {
+        if(sc == null)
+        {
+            return false;
+        }
+        sc.isValid = false;
+        sc.onTerm();
+        return scripts.remove(sc.id) != null;
+    }
+    
+    public void termSafe(Script sc)
+    {
+        if(sc == null)
+        {
+            return;
+        }
+        sc.isValid = false;
+        termQueue.add(sc.id);
+    }
+    
+    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.isValid = false;
+            
+        });
+        scripts.clear();
+    }
+    
+    public Collection<Script> getScripts()
+    {
+        return scripts.values();
+    }
+    
+    private Script startScript(String path, String end, boolean rEventBroadcast, Runnable onStart, Runnable onTerm)
+    { 
+        try
+        {            
+            List<String> code = Utils.readCode(path, end);
+            Script sc = new Script(logger, scheduler, code, path, idCounter++, onStart, onTerm, rEventBroadcast);
+            scripts.put(sc.id, sc);
+            sc.onStart();
+            sc.run();
+            term();
+            return sc;
+        }
+        catch(PreScriptException ex)
+        {
+            logger.print(ex.getLocalizedMessage(), ex, null, path, null, ex.getLine());
+            return null;
+        }
+    }
+    
+    public Script startScript(String path, String end, boolean rEventBroadcast)
+    { 
+        return startScript(path, end, rEventBroadcast, null, null);
+    }
+    
+    // -----------------------------------------------------------------------------------
+    // event
+    // -----------------------------------------------------------------------------------
+    
+    public void callEvent(String name, Consumer<Script> before, Consumer<Script> after, Predicate<Script> check)
+    {
+        scripts.values().stream()
+                .filter(sc -> sc.receiveEventBroadcast && !sc.isHolded && sc.isWaiting)
+                .filter(sc -> sc.isEventLoaded(name))
+                .filter(check)
+                .forEach(sc -> 
+                {
+                    sc.setVar("event", name);
+                    if(before != null)
+                    {
+                        before.accept(sc);
+                    }
+                    sc.run();
+                    if(after != null)
+                    {
+                        after.accept(sc);
+                    }
+                });
+        term();
+    }
+    
+    public void callEvent(String name, Consumer<Script> before, Consumer<Script> after)
+    {
+        callEvent(name, before, after, sc -> true);
+    }
+    
+    public boolean callEvent(String name, Script sc, Consumer<Script> before, Consumer<Script> after, boolean check)
+    {
+        if(sc.isEventLoaded(name) && !sc.isHolded && sc.isWaiting && check)
+        {
+            sc.setVar("event", name);
+            if(before != null)
+            {
+                before.accept(sc);
+            }
+            sc.run();
+            if(after != null)
+            {
+                after.accept(sc);
+            }
+            term();
+            return true;
+        }
+        return false;
+    }
+    
+    public boolean callEvent(String name, Script sc, Consumer<Script> before, Consumer<Script> after)
+    {
+        return callEvent(name, sc, before, after, true);
+    }
+}

+ 9 - 1
src/me/hammerle/snuviscript/exceptions/PreScriptException.java

@@ -2,8 +2,16 @@ package me.hammerle.snuviscript.exceptions;
 
 public class PreScriptException extends RuntimeException
 {
+    private final int line;
+    
     public PreScriptException(String message, int line) 
     {
-        super(message + " - line " + line);
+        super(message);
+        this.line = line;
+    }
+
+    public int getLine()
+    {
+        return line;
     }
 }

+ 5 - 14
test.sbasic

@@ -1,15 +1,6 @@
-list.new(a);
-list.add(a, 1);
-list.add(a, 36);
-list.add(a, 34);
-list.add(a, 6);
-
-list.add(getVar("a"), 6);
-
-print(a);
-print(getVar("a"));
-
-for(i, 0, list.getsize(a) - 1)
+print("Start");
+while(true)
 {
-    print(list.get(a, i));
-}
+    print("HEHE");
+}
+print("END")