Browse Source

init, new snuvi script, base is the basic interpreter

Kajetan Johannes Hammerle 6 years ago
commit
831f7ccd7f
27 changed files with 3440 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 73 0
      build.xml
  3. 3 0
      manifest.mf
  4. 17 0
      src/me/hammerle/snuviscript/SnuviScript.java
  5. 126 0
      src/me/hammerle/snuviscript/array/DynamicArray.java
  6. 25 0
      src/me/hammerle/snuviscript/code/BasicFunction.java
  7. 691 0
      src/me/hammerle/snuviscript/code/Compiler.java
  8. 203 0
      src/me/hammerle/snuviscript/code/DataUtils.java
  9. 83 0
      src/me/hammerle/snuviscript/code/Function.java
  10. 583 0
      src/me/hammerle/snuviscript/code/FunctionLoader.java
  11. 57 0
      src/me/hammerle/snuviscript/code/InputProvider.java
  12. 69 0
      src/me/hammerle/snuviscript/code/Instruction.java
  13. 31 0
      src/me/hammerle/snuviscript/code/JumpData.java
  14. 82 0
      src/me/hammerle/snuviscript/code/Script.java
  15. 50 0
      src/me/hammerle/snuviscript/code/SignInverter.java
  16. 229 0
      src/me/hammerle/snuviscript/code/Syntax.java
  17. 35 0
      src/me/hammerle/snuviscript/constants/ConstantBoolean.java
  18. 51 0
      src/me/hammerle/snuviscript/constants/ConstantFraction.java
  19. 45 0
      src/me/hammerle/snuviscript/constants/ConstantNull.java
  20. 32 0
      src/me/hammerle/snuviscript/constants/ConstantString.java
  21. 9 0
      src/me/hammerle/snuviscript/exceptions/PreScriptException.java
  22. 695 0
      src/me/hammerle/snuviscript/math/Fraction.java
  23. 24 0
      src/me/hammerle/snuviscript/variable/ArrayVariable.java
  24. 45 0
      src/me/hammerle/snuviscript/variable/LocalArrayVariable.java
  25. 81 0
      src/me/hammerle/snuviscript/variable/LocalVariable.java
  26. 82 0
      src/me/hammerle/snuviscript/variable/Variable.java
  27. 16 0
      test.sbasic

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/nbproject
+/build
+/dist

+ 73 - 0
build.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See commented blocks below for -->
+<!-- some examples of how to customize the build. -->
+<!-- (If you delete it and reopen the project it will be recreated.) -->
+<!-- By default, only the Clean and Build commands use this build script. -->
+<!-- Commands such as Run, Debug, and Test only use this build script if -->
+<!-- the Compile on Save feature is turned off for the project. -->
+<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
+<!-- in the project's Project Properties dialog box.-->
+<project name="SnuviScriptRecoded" default="default" basedir=".">
+    <description>Builds, tests, and runs the project SnuviScriptRecoded.</description>
+    <import file="nbproject/build-impl.xml"/>
+    <!--
+
+    There exist several targets which are by default empty and which can be 
+    used for execution of your tasks. These targets are usually executed 
+    before and after some main targets. They are: 
+
+      -pre-init:                 called before initialization of project properties
+      -post-init:                called after initialization of project properties
+      -pre-compile:              called before javac compilation
+      -post-compile:             called after javac compilation
+      -pre-compile-single:       called before javac compilation of single file
+      -post-compile-single:      called after javac compilation of single file
+      -pre-compile-test:         called before javac compilation of JUnit tests
+      -post-compile-test:        called after javac compilation of JUnit tests
+      -pre-compile-test-single:  called before javac compilation of single JUnit test
+      -post-compile-test-single: called after javac compilation of single JUunit test
+      -pre-jar:                  called before JAR building
+      -post-jar:                 called after JAR building
+      -post-clean:               called after cleaning build products
+
+    (Targets beginning with '-' are not intended to be called on their own.)
+
+    Example of inserting an obfuscator after compilation could look like this:
+
+        <target name="-post-compile">
+            <obfuscate>
+                <fileset dir="${build.classes.dir}"/>
+            </obfuscate>
+        </target>
+
+    For list of available properties check the imported 
+    nbproject/build-impl.xml file. 
+
+
+    Another way to customize the build is by overriding existing main targets.
+    The targets of interest are: 
+
+      -init-macrodef-javac:     defines macro for javac compilation
+      -init-macrodef-junit:     defines macro for junit execution
+      -init-macrodef-debug:     defines macro for class debugging
+      -init-macrodef-java:      defines macro for class execution
+      -do-jar:                  JAR building
+      run:                      execution of project 
+      -javadoc-build:           Javadoc generation
+      test-report:              JUnit report generation
+
+    An example of overriding the target for project execution could look like this:
+
+        <target name="run" depends="SnuviScriptRecoded-impl.jar">
+            <exec dir="bin" executable="launcher.exe">
+                <arg file="${dist.jar}"/>
+            </exec>
+        </target>
+
+    Notice that the overridden target depends on the jar target and not only on 
+    the compile target as the regular run target does. Again, for a list of available 
+    properties which you can use, check the target you are overriding in the
+    nbproject/build-impl.xml file. 
+
+    -->
+</project>

+ 3 - 0
manifest.mf

@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+

+ 17 - 0
src/me/hammerle/snuviscript/SnuviScript.java

@@ -0,0 +1,17 @@
+package me.hammerle.snuviscript;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import me.hammerle.snuviscript.code.Script;
+
+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(lines);
+        System.out.println("\n" + sc.run());
+    }  
+}

+ 126 - 0
src/me/hammerle/snuviscript/array/DynamicArray.java

@@ -0,0 +1,126 @@
+package me.hammerle.snuviscript.array;
+
+import me.hammerle.snuviscript.code.InputProvider;
+import me.hammerle.snuviscript.variable.Variable;
+import java.lang.reflect.Array;
+import me.hammerle.snuviscript.code.DataUtils;
+import me.hammerle.snuviscript.code.Script;
+import me.hammerle.snuviscript.variable.LocalVariable;
+import me.hammerle.snuviscript.math.Fraction;
+
+public class DynamicArray extends InputProvider
+{
+    protected Variable var;
+    protected InputProvider[] input;
+    
+    public DynamicArray(Variable var, InputProvider[] input)
+    {
+        this.var = var;
+        this.input = input;
+    }
+
+    public int getLength(Script sc)
+    {
+        return Array.getLength(var.getArray(sc));
+    }
+    
+    public void init(Script sc)
+    {
+        int[] dims = new int[input.length];
+        for(int i = 0; i < dims.length; i++)
+        {
+            dims[i] = input[i].getInt(sc);
+        }
+        var.set(sc, Array.newInstance(Object.class, dims));
+    }
+    
+    @Override
+    public Object getArray(Script sc)
+    {
+        Object ob = var.getArray(sc);
+        for(InputProvider in : input) 
+        {
+            ob = Array.get(ob, in.getInt(sc));
+        }
+        return ob;
+    }
+    
+    public Object getLastArray(Script sc)
+    {
+        Object ob = var.getArray(sc);
+        int end = input.length - 1;
+        for(int j = 0; j < end; j++)
+        {
+            ob = Array.get(ob, input[j].getInt(sc));
+        }
+        return ob;
+    }
+    
+    @Override
+    public void set(Script sc, Object o) 
+    {
+        Array.set(getLastArray(sc), input[input.length - 1].getInt(sc), o);
+    }
+
+    @Override
+    public Object get(Script sc) 
+    {
+        return Array.get(getLastArray(sc), input[input.length - 1].getInt(sc));
+    }
+    
+    @Override
+    public Fraction getFraction(Script sc) 
+    {
+        return (Fraction) get(sc);
+    }
+    
+    @Override
+    public int getInt(Script sc) 
+    {
+        return getFraction(sc).intValue();
+    }
+    
+    @Override
+    public double getDouble(Script sc) 
+    {
+        return getFraction(sc).doubleValue();
+    }
+    
+    @Override
+    public String getString(Script sc) 
+    {
+        Object last = getLastArray(sc);
+        int index = input[input.length - 1].getInt(sc);
+        try
+        {
+            return String.valueOf(Array.get(last, index));
+        }
+        catch(IllegalArgumentException ex)
+        {
+            return DataUtils.getArrayString(Array.get(last, index));
+        }
+    }
+
+    @Override
+    public String toString() 
+    {
+        StringBuilder sb = new StringBuilder(var.getName());
+        sb.append("#");
+        if(var instanceof LocalVariable)
+        {
+            sb.append("L");
+        }
+        sb.append("[");
+        for(InputProvider in : input)
+        {
+            sb.append(in);
+            sb.append(", ");
+        }
+        if(input.length > 0)
+        {
+            sb.delete(sb.length() - 2, sb.length());
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}

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

@@ -0,0 +1,25 @@
+package me.hammerle.snuviscript.code;
+
+import java.util.function.BiFunction;
+
+public class BasicFunction 
+{
+    private final String name;
+    private final BiFunction<Script, InputProvider[], Object> f;
+    
+    public BasicFunction(String name, BiFunction<Script, InputProvider[], Object> f)
+    {
+        this.name = name;
+        this.f = f;
+    }
+    
+    public String getName()
+    {
+        return name;
+    }
+    
+    public Object execute(Script sc, InputProvider[] input)
+    {
+        return f.apply(sc, input);
+    }
+}

+ 691 - 0
src/me/hammerle/snuviscript/code/Compiler.java

@@ -0,0 +1,691 @@
+package me.hammerle.snuviscript.code;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+import me.hammerle.snuviscript.constants.ConstantFraction;
+import me.hammerle.snuviscript.constants.ConstantNull;
+import me.hammerle.snuviscript.constants.ConstantString;
+import me.hammerle.snuviscript.array.DynamicArray;
+import me.hammerle.snuviscript.constants.ConstantBoolean;
+import me.hammerle.snuviscript.variable.ArrayVariable;
+import me.hammerle.snuviscript.variable.LocalArrayVariable;
+import me.hammerle.snuviscript.variable.LocalVariable;
+import me.hammerle.snuviscript.variable.Variable;
+import me.hammerle.snuviscript.exceptions.PreScriptException;
+import me.hammerle.snuviscript.math.Fraction;
+
+public class Compiler 
+{
+    public static Instruction[] compile(Script sc, List<String> sCode, HashMap<String, Integer> labels, boolean locale, int lineOffset)
+    {
+        Compiler compiler = new Compiler(sc, sCode, labels, locale);
+        compiler.lineOffset = lineOffset;
+        return compiler.compile();
+    }
+    
+    private final List<String> sCode;
+    private final HashMap<String, Variable> vars;
+    private final HashMap<String, Variable> localVars;
+    private final HashMap<String, Integer> labels;
+    
+    private final LinkedList<Instruction> code;
+    private int line;
+    private int lineOffset;
+    private int layer;
+    
+    private class JumpWrapper
+    {
+        private final JumpData data;
+        private final String function;
+        
+        public JumpWrapper(JumpData data, String function)
+        {
+            this.data = data;
+            this.function = function;
+        }
+    }
+    
+    private final Stack<JumpWrapper> jumps;
+    private final Stack<JumpWrapper> loopJumps;
+    private final LinkedList<JumpData> breakContinueJumps;
+    private final Script parentScript;
+    
+    private final HashMap<String, String> strings;
+    private int stringCounter;
+    
+    private final boolean locale;
+    
+    private Compiler(Script sc, List<String> sCode, HashMap<String, Integer> labels, boolean locale)
+    {
+        this.parentScript = sc;
+        this.sCode = sCode;
+        this.vars = new HashMap<>();
+        this.localVars = new HashMap<>();
+        this.labels = labels;
+        
+        this.code = new LinkedList<>();
+        this.line = 0;
+        this.layer = 0;
+        this.jumps = new Stack<>();
+        this.loopJumps = new Stack<>();
+        this.breakContinueJumps = new LinkedList<>();
+        this.strings = new HashMap<>();
+        this.stringCounter = 0;
+        this.locale = locale;
+    }
+    
+    private void addCodeInstruction(String function, InputProvider[] input)
+    {
+        code.add(new Instruction(line + lineOffset, (byte) layer, FunctionLoader.getFunction(function), input));
+    }
+    
+    private Instruction[] compile()
+    {
+        int size = sCode.size();
+        System.out.println("__________________________________");
+        
+        StringBuilder sb = new StringBuilder();
+        String replacement;
+        String check;
+        int pos;
+        int old = 0;
+        boolean text = false;
+        boolean comment = false;
+        
+        for(line = 0; line < size; line++)
+        {
+            pos = sb.length();
+            sb.append(sCode.get(line));
+            
+            while(pos < sb.length())
+            {
+                if(comment)
+                {
+                    if(pos + 1 < sb.length() && sb.charAt(pos) == '*' && sb.charAt(pos + 1) == '/')
+                    {
+                        comment = false;
+                        sb.delete(old, pos + 2);
+                    }
+                    pos++;
+                    continue;
+                }
+                else if(text)
+                {
+                    if(sb.charAt(pos) == '"')
+                    {
+                        replacement = "#" + stringCounter++;
+                        strings.put(replacement, sb.substring(old, pos + 1));
+                        text = false;
+                        sb.replace(old, pos + 1, replacement);
+                        pos = old - 1 + replacement.length();
+                    }
+                    pos++;
+                    continue;
+                }
+                switch(sb.charAt(pos))
+                {
+                    case '/':
+                        if(pos + 1 < sb.length())
+                        {
+                            switch(sb.charAt(pos + 1))
+                            {
+                                case '/':
+                                    sb.delete(pos, sb.length());
+                                    break;
+                                case '*':
+                                    comment = true;
+                                    old = pos;
+                                    pos++;
+                                    break;
+                            }
+                        }
+                        break;
+                    case '}':
+                        sb.delete(0, pos + 1);
+                        pos = -1;
+                        layer--;
+                        if(jumps.isEmpty())
+                        {
+                            throw new PreScriptException("} without a corresponding function and / or {", line);
+                        }
+                        JumpWrapper data = jumps.pop();
+                        switch(data.function)
+                        {
+                            case "if":
+                            {
+                                data.data.setRelativeJump(code.size());
+                                break;
+                            }
+                            case "for":
+                            {
+                                loopJumps.pop();
+                                createBreakContinue(code.size());
+                                JumpData jump = data.data;
+                                jump.setRelativeJump(code.size());
+                                addCodeInstruction("next", new InputProvider[] {new JumpData(-jump.getInt(null) - 1)});
+                                break;
+                            }
+                            case "while":
+                            {
+                                loopJumps.pop();
+                                createBreakContinue(code.size());
+
+                                JumpData jump = data.data;
+                                jump.setRelativeJump(code.size() + 1);
+                                addCodeInstruction("wend", new InputProvider[] {new JumpData(-jump.getInt(null) - 1)});
+                                break;
+                            }
+                        }
+                        break;
+                    case '{':
+                        int currentJumps = jumps.size();
+                        check = sb.toString();
+                        if(check.startsWith("function "))
+                        {
+                            if(parentScript.subScript)
+                            {
+                                throw new PreScriptException("function not allowed in another function", line);
+                            }
+                            int index = check.indexOf("(");
+                            if(index == -1)
+                            {
+                                throw new PreScriptException("missing function syntax", line);
+                            }
+                            String function = check.substring(9, index);
+                            int endIndex = check.indexOf(")", index);
+                            if(index == -1)
+                            {
+                                throw new PreScriptException("missing function syntax", line);
+                            }
+                            String[] inputs;
+                            if(index + 1 == endIndex)
+                            {
+                                inputs = new String[0];
+                            }
+                            else
+                            {
+                                inputs = check.substring(index + 1, endIndex).split("[ ]*,[ ]*");
+                            }
+                            ArrayList<String> subList = new ArrayList<>();
+                            int bCounter = 1;
+                            String subLine = check;
+                            pos++;
+                            
+                            int oldLine = line;
+                            out: while(true)
+                            {
+                                old = pos;
+                                while(pos < subLine.length())
+                                {
+                                    switch(subLine.charAt(pos))
+                                    {
+                                        case '"':
+                                            text = !text;
+                                            break;
+                                        case '{':
+                                            if(!text)
+                                            {
+                                                bCounter++;
+                                            }
+                                            break;
+                                        case '}':
+                                            if(!text)
+                                            {
+                                                bCounter--;
+                                                if(bCounter == 0)
+                                                {
+                                                    subList.add(subLine.substring(old, pos).trim());
+                                                    sb = new StringBuilder();
+                                                    sCode.set(line, subLine.substring(pos + 1));
+                                                    line--;
+                                                    break out;
+                                                }
+                                            }
+                                            break;
+                                    }
+                                    pos++;
+                                }
+                                subList.add(subLine.substring(old, pos).trim());
+                                line++;
+                                if(line >= sCode.size())
+                                {
+                                    throw new PreScriptException("{ without }", line);
+                                }
+                                pos = 0;
+                                subLine = sCode.get(line);
+                            }
+                            
+                            Script sub = new Script(subList, inputs, parentScript, oldLine);
+                            parentScript.subScripts.put(function, sub);
+                        }
+                        else
+                        {
+                            check = sb.substring(0, pos);
+                            compileLine(check);
+                            sb.delete(0, pos + 1);
+                            layer++;
+                            if(currentJumps == jumps.size())
+                            {
+                                throw new PreScriptException("{ without a corresponding function", line);
+                            }
+                        }
+                        pos = -1;
+                        break;
+                    case ';':
+                        compileLine(sb.substring(0, pos).trim());
+                        sb.delete(0, pos + 1);
+                        pos = -1;
+                        break;
+                    case '"':
+                        text = true;
+                        old = pos;
+                        break;
+                }
+                pos++;
+            }
+        }
+        
+        System.out.println("__________________________________");
+        
+        Instruction[] input = code.toArray(new Instruction[code.size()]);
+        
+        for(Instruction in : input)
+        {
+            System.out.println(in);
+        }
+        System.out.println("__________________________________");
+        labels.entrySet().stream().forEach((e) -> 
+        {
+            System.out.println("LABEL " + e.getKey() + " " + e.getValue());
+        });
+        System.out.println("__________________________________");
+        return input;
+    }
+    
+    private void compileLine(String currentCode)
+    {
+        if(currentCode.startsWith("'"))
+        {
+            return;
+        }
+        //System.out.println(">>>"  + currentCode);
+        String[] parts = DataUtils.split(strings, currentCode, line);
+        //System.out.println(">>> " + String.join("_", parts));
+        
+        if(parts.length == 0)
+        {
+            return;
+        }
+        else if(parts[0].equals("return"))
+        {
+            addCodeInstruction("return", compileFunction(parts, true));
+            return;
+        }
+        else if(parts[0].startsWith("@"))
+        {
+            if(parts.length > 1)
+            {
+                throw new PreScriptException("arguments after label", line);
+            }
+            labels.put(parts[0].substring(1), code.size() - 1);
+            return;
+        }
+        
+        String input;
+        if(parts.length == 1)
+        {
+            int bPos = parts[0].indexOf('(');
+            if(bPos != -1)
+            {
+                input = parts[0].substring(0, bPos);
+                parts = DataUtils.split(strings, parts[0].substring(bPos + 1, parts[0].length() - 1), line);
+            }
+            else
+            {
+                switch(parts[0])
+                {
+                    case "while":
+                        throw new PreScriptException("missing syntax at while", line);
+                    case "if":
+                        throw new PreScriptException("missing syntax at if", line);
+                    case "for":
+                        throw new PreScriptException("missing syntax at for", line);             
+                    case "break":
+                    {
+                        if(loopJumps.isEmpty())
+                        {
+                            throw new IllegalStateException("break without a loop");
+                        }
+                        JumpData jump = new JumpData(code.size() - 1);
+                        breakContinueJumps.add(jump);
+                        addCodeInstruction("break", new InputProvider[] {jump});
+                        return;
+                    }
+                    case "continue":
+                    {
+                        if(loopJumps.isEmpty())
+                        {
+                            throw new IllegalStateException("continue without a loop");
+                        }
+                        JumpData jump = new JumpData(code.size());
+                        breakContinueJumps.add(jump);
+                        addCodeInstruction("continue", new InputProvider[] {jump});
+                        return;
+                    }
+                }
+                return;
+            }
+        }
+        else
+        {
+            switch(parts[1])
+            {           
+                case "=":
+                case "+=":
+                case "-=":
+                case "*=":
+                case "/=":
+                case "%=":
+                case "<<=":
+                case ">>=":
+                case "&=":
+                case "^=":
+                case "|=":
+                {
+                    input = parts[1];
+                    parts[1] = ",";
+                    break;
+                }
+                default:
+                    throw new PreScriptException("unknown operation " + parts[1], line);
+            }
+        }
+        switch(input)
+        {
+            case "break":
+                throw new PreScriptException("break does not accept arguments", line);
+            case "continue":
+                throw new PreScriptException("continue does not accept arguments", line);      
+        }
+        //System.out.println(input + "  " + String.join("__", parts));
+        
+        switch(input)
+        {
+            case "if":
+                createIf(parts);
+                break;
+            case "for":
+                createFor(parts);
+                break;
+            case "while":
+                createWhile(parts);
+                break;
+            default:
+                addCodeInstruction(input, compileFunction(parts, false));
+        }
+    }
+    
+    private void addSyntax(LinkedList<InputProvider> list, Syntax sy)
+    {
+        int pars = sy.getParameters();
+        if(pars > list.size())
+        {
+            throw new PreScriptException("missing syntax argument", line);
+        }
+        if(sy == Syntax.UNARY_SUB)
+        {
+            list.add(new SignInverter(list.pollLast()));
+            return;
+        }
+        InputProvider[] input = new InputProvider[pars];
+        for(int j = input.length - 1; j >= 0; j--)
+        {
+            input[j] = list.pollLast();
+        }
+        list.add(new Function(FunctionLoader.getFunction(sy.getFunction()), input));
+    }
+    
+    private void validateStackCounter(int stackCounter)
+    {
+        if(stackCounter < 0)
+        {
+            throw new PreScriptException("missing syntax argument", line);
+        }
+    }
+    
+    private InputProvider[] compileFunction(String[] parts, boolean first)
+    {
+        LinkedList<InputProvider> list = new LinkedList<>();
+        int stackCounter = 0;
+        
+        Stack<Syntax> syntax = new Stack<>();
+        int bottom = first ? 1 : 0;
+        Syntax sy;
+        for(int i = bottom; i < parts.length; i++)
+        {
+            if(parts[i].equals(","))
+            {
+                // finding a comma means pushing all syntax functions
+                while(!syntax.isEmpty())
+                {
+                    addSyntax(list, syntax.pop());
+                }
+                stackCounter = 0;
+                continue;
+            }
+            sy = Syntax.getSyntax(parts[i]);
+            if(sy != Syntax.UNKNOWN && sy != Syntax.MAYBE)
+            {
+                if(stackCounter <= 0)
+                {
+                    if(sy == Syntax.SUB)
+                    {
+                        sy = Syntax.UNARY_SUB;
+                    }
+                    else
+                    {
+                        throw new PreScriptException("missing syntax argument", line);
+                    }
+                }
+                // pushing weaker functions
+                int weight = sy.getWeight();
+                while(!syntax.isEmpty() && syntax.peek().getWeight() <= weight)
+                {
+                    addSyntax(list, syntax.pop());
+                }
+                validateStackCounter(stackCounter);
+                syntax.add(sy);
+                stackCounter -= sy.getParameters() - 1;
+                continue;
+            }
+            stackCounter++;
+            list.add(convertString(parts[i]));
+        }
+        // pushing left over syntax functions because no comma happened
+        while(!syntax.isEmpty())
+        {
+            addSyntax(list, syntax.pop());
+        }
+        validateStackCounter(stackCounter);
+        return list.toArray(new InputProvider[list.size()]);
+    }
+    
+    private InputProvider convertString(String input)
+    {
+        if(input.startsWith("@"))
+        {
+            return new ConstantString(input.substring(1));
+        }
+        else if(input.startsWith("\"") && input.endsWith("\""))
+        {
+            return new ConstantString(input.substring(1, input.length() - 1));
+        }
+        else if(input.equals("true"))
+        {
+            return ConstantBoolean.TRUE;
+        }
+        else if(input.equals("false"))
+        {
+            return ConstantBoolean.FALSE;
+        }
+        else if(input.equals("null"))
+        {
+            return ConstantNull.NULL;
+        }
+        else if(DataUtils.isNumber(input))
+        {
+            return new ConstantFraction(Fraction.fromDouble(Double.parseDouble(input)));
+        }
+        else if(DataUtils.isFunction(input))
+        {
+            int bPos = input.indexOf('(');
+            String[] parts = DataUtils.split(strings, input.substring(bPos + 1, input.length() - 1), line);
+            if(parts.length > 0)
+            {
+                return new Function(FunctionLoader.getFunction(input.substring(0, bPos)), compileFunction(parts, false));
+            }
+            else
+            {
+                return new Function(FunctionLoader.getFunction(input.substring(0, bPos)), new InputProvider[0]);
+            }
+        }
+        else if(DataUtils.isArray(input))
+        {
+            int bPos = input.indexOf('[');
+            String[] parts = DataUtils.split(strings, input.substring(bPos + 1, input.length() - 1), line);
+            if(parts.length > 0)
+            {
+                return createArray(input.substring(0, bPos), compileFunction(parts, false));
+            }
+            else
+            {
+                return createArray(input.substring(0, bPos), new InputProvider[0]);
+            }
+        }
+        else
+        {
+            return getOrCreateVariable(input);
+        }
+    }
+    
+    private Variable getOrCreateVariable(String var)
+    {
+        if(locale)
+        {
+            Variable oldVar = localVars.get(var);
+            if(oldVar == null)
+            {
+                oldVar = new LocalVariable(var);
+                localVars.put(var, oldVar);
+            }
+            return oldVar;
+        }
+        else
+        {
+            Variable oldVar = vars.get(var);
+            if(oldVar == null)
+            {
+                oldVar = new Variable(var);
+                vars.put(var, oldVar);
+            }
+            return oldVar;
+        }
+    }
+    
+    private DynamicArray createArray(String var, InputProvider[] in)
+    {
+        if(locale)
+        {
+            Variable oldVar = localVars.get(var);
+            if(oldVar == null)
+            {
+                oldVar = new LocalArrayVariable(var);
+                localVars.put(var, oldVar);
+            }
+            return new DynamicArray(oldVar, in);    
+        }
+        else
+        {
+            Variable oldVar = vars.get(var);
+            if(oldVar == null)
+            {
+                oldVar = new ArrayVariable(var);
+                vars.put(var, oldVar);
+            }
+            return new DynamicArray(oldVar, in);
+        }
+    }
+    
+    private void createIf(String[] parts)
+    {
+        InputProvider[] input = compileFunction(parts, false);
+        InputProvider[] realInput = new InputProvider[input.length + 1];
+
+        System.arraycopy(input, 0, realInput, 0, input.length);
+        JumpData jump = new JumpData(code.size());
+        realInput[input.length] = jump;
+        jumps.push(new JumpWrapper(jump, "if"));
+        
+        addCodeInstruction("if", realInput);
+    }
+    
+    private void createFor(String[] parts)
+    {
+        // expected syntax
+        // for(var, start, end, step)
+        // for(var, start, end)
+        InputProvider[] input = compileFunction(parts, false);
+        if(input.length != 3 && input.length != 4)
+        {
+            throw new PreScriptException("missing 'for' syntax at", line);
+        }
+        InputProvider[] realInput = new InputProvider[5];
+
+        System.arraycopy(input, 0, realInput, 0, input.length);
+        
+        if(input.length == 3)
+        {
+            realInput[3] = new ConstantFraction(new Fraction(1));
+        }
+        
+        JumpData jump = new JumpData(code.size());
+        realInput[4] = jump;
+        JumpWrapper wrapper = new JumpWrapper(jump, "for");
+        jumps.push(wrapper);
+        loopJumps.push(wrapper);
+        
+        addCodeInstruction("for", realInput);
+    }
+    
+    private void createWhile(String[] parts)
+    {
+        // expected syntax
+        // while(condition)
+        InputProvider[] input = compileFunction(parts, false);
+        if(input.length != 1)
+        {
+            throw new PreScriptException("invalid conditions at 'while'", line);
+        }
+        InputProvider[] realInput = new InputProvider[2];
+        realInput[0] = input[0];
+        
+        JumpData jump = new JumpData(code.size());
+        realInput[1] = jump;
+        
+        JumpWrapper wrapper = new JumpWrapper(jump, "while");
+        jumps.push(wrapper);
+        loopJumps.push(wrapper);
+
+        addCodeInstruction("while", realInput);
+    }
+    
+    private void createBreakContinue(int current)
+    {
+        breakContinueJumps.forEach(jump -> jump.setRelativeJump(current));
+        breakContinueJumps.clear();
+    }
+}
+// 811

+ 203 - 0
src/me/hammerle/snuviscript/code/DataUtils.java

@@ -0,0 +1,203 @@
+package me.hammerle.snuviscript.code;
+
+import java.lang.reflect.Array;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.regex.Pattern;
+import me.hammerle.snuviscript.exceptions.PreScriptException;
+
+public class DataUtils 
+{
+    // - in the number is handled somewhere else
+    private static final Pattern NUMBER_PATTERN = Pattern.compile("^[0-9]*[.]{0,1}[0-9]*");
+    
+    public static boolean isNumber(String s)
+    {
+        return NUMBER_PATTERN.matcher(s).matches();
+    }
+    
+    private static final Pattern FUNCTION_PATTERN = Pattern.compile("^[a-zA-Z]*\\(.*\\)");
+    
+    public static boolean isFunction(String s)
+    {
+        return FUNCTION_PATTERN.matcher(s).matches();
+    }
+    
+    private static final Pattern ARRAY_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]*\\[[^\\]]*\\]");
+    
+    public static boolean isArray(String s)
+    {
+        return ARRAY_PATTERN.matcher(s).matches();
+    }
+    
+    // -------------------------------------------------------------------------
+    // line splitter
+    // -------------------------------------------------------------------------
+      
+    private static void addNonEmptyString(HashMap<String, String> strings, LinkedList<String> list, String s)
+    {
+        s = s.trim();
+        if(!s.isEmpty())
+        {
+            if(s.startsWith("#"))
+            {
+                String text = strings.get(s);
+                if(text != null)
+                {
+                    list.add(text);
+                    return;
+                }
+            }
+            list.add(s);
+        }
+    }
+    
+    private static int findNextClosingBracket(int pos, StringBuilder sb, int line)
+    {
+        int brackets = 0;
+        int length = sb.length();
+        while(pos < length)
+        {
+            switch(sb.charAt(pos))
+            {
+                case ')':
+                    brackets--;
+                    if(brackets == 0)
+                    {
+                        return pos;
+                    }
+                    else if(brackets < 0)
+                    {
+                        throw new PreScriptException(") without (", line);
+                    }
+                    break;
+                case '(':
+                    brackets++;
+                    break;
+            }
+            pos++;
+        }
+        throw new PreScriptException("( without )", line);
+    }
+    
+    private static int findNextClosingSBracket(int pos, StringBuilder sb, int line)
+    {
+        int brackets = 0;
+        int length = sb.length();
+        while(pos < length)
+        {
+            switch(sb.charAt(pos))
+            {
+                case ']':
+                    brackets--;
+                    if(brackets == 0)
+                    {
+                        return pos;
+                    }
+                    else if(brackets < 0)
+                    {
+                        throw new PreScriptException("] without [", line);
+                    }
+                    break;
+                case '[':
+                    brackets++;
+                    break;
+            }
+            pos++;
+        }
+        throw new PreScriptException("[ without ]", line);
+    }
+    
+    public static String[] split(HashMap<String, String> strings, String s, int line)
+    {
+        LinkedList<String> list = new LinkedList<>();
+        
+        int old = 0;
+        int pos = 0;
+        
+        StringBuilder sb = new StringBuilder(s);
+        int length = sb.length();
+        char c;
+        while(pos < length)
+        {
+            c = sb.charAt(pos);
+            if(!Character.isLetterOrDigit(c))
+            {
+                switch(c)
+                {
+                    case '_':
+                    case '.':
+                    case '#':
+                    case '@':
+                        break;
+                    case ')':
+                        throw new PreScriptException(") without (", line);
+                    case '(':   
+                        pos = findNextClosingBracket(pos, sb, line) + 1;
+                        addNonEmptyString(strings, list, sb.substring(old, pos));
+                        old = pos;
+                        continue;
+                    case ']':
+                        throw new PreScriptException("] without [", line);
+                    case '[':   
+                        pos = findNextClosingSBracket(pos, sb, line) + 1;
+                        addNonEmptyString(strings, list, sb.substring(old, pos));
+                        old = pos;
+                        continue;
+                    case ' ':
+                        addNonEmptyString(strings, list, sb.substring(old, pos));
+                        old = pos + 1;
+                        pos = old;
+                        continue;
+                    case ',':   
+                        addNonEmptyString(strings, list, sb.substring(old, pos));
+                        addNonEmptyString(strings, list, ",");
+                        old = pos + 1;
+                        pos = old;
+                        continue;
+                    default:
+                        addNonEmptyString(strings, list, sb.substring(old, pos));
+                        //System.out.println(old + " " + pos);
+                        old = pos;
+                        pos++;
+                        while(pos <= length && Syntax.getSyntax(sb.substring(old, pos)) != Syntax.UNKNOWN)
+                        {
+                            pos++;
+                        }
+                        pos--;
+                        if(old == pos)
+                        {
+                            throw new PreScriptException("unknown syntax " + c, line);
+                        }
+                        addNonEmptyString(strings, list, sb.substring(old, pos));
+                        old = pos;
+                        continue;
+                }
+            }
+            pos++;
+        }
+        if(old < length)
+        {
+            addNonEmptyString(strings, list, sb.substring(old));
+        }
+        
+        return list.toArray(new String[list.size()]);
+    }
+    
+    public static String getArrayString(Object array)
+    {
+        StringBuilder sb = new StringBuilder("[");
+        int length = Array.getLength(array) - 1;
+        for(int i = 0; i < length; i++)
+        {
+            sb.append(Array.get(array, i));
+            sb.append(", ");
+        }
+        if(length > 0)
+        {
+            sb.append(Array.get(array, length));
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 83 - 0
src/me/hammerle/snuviscript/code/Function.java

@@ -0,0 +1,83 @@
+package me.hammerle.snuviscript.code;
+
+import me.hammerle.snuviscript.math.Fraction;
+import me.hammerle.snuviscript.variable.Variable;
+import me.hammerle.snuviscript.variable.ArrayVariable;
+import me.hammerle.snuviscript.variable.LocalArrayVariable;
+
+public class Function extends InputProvider
+{
+    private final BasicFunction function;
+    private InputProvider[] input;
+    
+    public Function(BasicFunction function, InputProvider[] input)
+    {
+        this.function = function;
+        this.input = input;
+    }
+
+    @Override
+    public String toString() 
+    {
+        StringBuilder sb = new StringBuilder(function.getName());
+        sb.append("(");
+        for(InputProvider in : input)
+        {
+            sb.append(in);
+            sb.append(", ");
+        }
+        if(input.length > 0)
+        {
+            sb.delete(sb.length() - 2, sb.length());
+        }
+        sb.append(")");
+        return sb.toString();
+    }
+
+    @Override
+    public Object get(Script sc) 
+    {
+        return function.execute(sc, input);
+    }
+    
+    @Override
+    public Object getArray(Script sc)
+    {
+        Object o = function.execute(sc, input);
+        if(o instanceof ArrayVariable || o instanceof LocalArrayVariable)
+        {
+            return o;
+        }
+        return null;
+    }
+    
+    @Override
+    public Fraction getFraction(Script sc)
+    {
+        return (Fraction) function.execute(sc, input);
+    }
+    
+    @Override
+    public int getInt(Script sc)
+    {
+        return (Integer) function.execute(sc, input);
+    }
+    
+    @Override
+    public double getDouble(Script sc) 
+    {
+        return ((Number) function.execute(sc, input)).doubleValue();
+    }
+    
+    @Override
+    public String getString(Script sc)
+    {
+        return String.valueOf(function.execute(sc, input));
+    }
+    
+    @Override
+    public Variable getVariable(Script sc)
+    {
+        return (Variable) function.execute(sc, input);
+    }
+}

+ 583 - 0
src/me/hammerle/snuviscript/code/FunctionLoader.java

@@ -0,0 +1,583 @@
+package me.hammerle.snuviscript.code;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.stream.Collectors;
+import me.hammerle.snuviscript.array.DynamicArray;
+import me.hammerle.snuviscript.variable.ArrayVariable;
+import me.hammerle.snuviscript.variable.Variable;
+import me.hammerle.snuviscript.math.Fraction;
+
+public class FunctionLoader 
+{
+    private static final HashMap<String, BasicFunction> FUNCTIONS = new HashMap<>();
+    
+    public static void registerFunction(String name, BasicFunction function)
+    {
+        FUNCTIONS.put(name, function);
+    }
+    
+    public static void registerFunction(BasicFunction function)
+    {
+        registerFunction(function.getName(), function);
+    }
+    
+    public static void registerAlias(String original, String alias)
+    {
+        FUNCTIONS.put(alias, FUNCTIONS.get(original));
+    }
+    
+    public static BasicFunction getFunction(String function)
+    {
+        return FUNCTIONS.getOrDefault(function, new BasicFunction(function, (sc, in) -> 
+        {
+            Script sub = sc.subScripts.get(function);
+            if(sub == null)
+            {
+                throw new NullPointerException("function " + function + " does not exist");
+            }
+            // push storage for local vars
+            HashMap<String, Variable> vars = new HashMap<>();
+            if(in.length != sub.subScriptInput.length)
+            {
+                throw new NullPointerException("invalid number of input for function " + function);
+            }
+            // generate local vars
+            String s;
+            Variable v;
+            for(int i = 0; i < in.length; i++)
+            {
+                s = sub.subScriptInput[i];
+                if(in[i].isArray(sc))
+                {
+                    v = new ArrayVariable(s);
+                    v.set(sc, in[i].getArray(sc));
+                }
+                else
+                {
+                    v = new Variable(s);
+                    v.set(sc, in[i].get(sc));
+                }
+                vars.put(s, v);
+            }
+            
+            sub.localVars.push(vars);
+            // saving line for return
+            int line = sub.currentLine;
+            // set starting line for current run
+            sub.currentLine = 0;
+            // run subscript and save return value
+            Object r = sub.run();
+            // return back to previous line
+            sub.currentLine = line;
+            // pop storage for local vars
+            sub.localVars.pop();
+            return r;
+        }));
+    }
+    
+    private static final Random[] RND;
+    
+    static
+    {
+        RND = new Random[8];
+        for(int i = 0; i < 8; i++)
+        {
+            RND[i] = new Random();
+        }
+        // ---------------------------------------------------------------------
+        // brackets
+        // ---------------------------------------------------------------------
+        registerFunction("", new BasicFunction("", (sc, in) -> 
+        {
+            return in[0].get(sc);
+        }));
+        // ---------------------------------------------------------------------
+        // elementary arithmetic
+        // ---------------------------------------------------------------------
+        registerFunction("+", new BasicFunction("ADD", (sc, in) -> 
+        {
+            return in[0].getFraction(sc).add(in[1].getFraction(sc));
+        }));
+        registerFunction("-", new BasicFunction("SUB", (sc, in) -> 
+        {
+            return in[0].getFraction(sc).sub(in[1].getFraction(sc));
+        }));
+        registerFunction("*", new BasicFunction("MUL", (sc, in) -> 
+        {
+            return in[0].getFraction(sc).mul(in[1].getFraction(sc));
+        }));
+        registerFunction("/", new BasicFunction("DIV", (sc, in) -> 
+        {
+            return in[0].getFraction(sc).div(in[1].getFraction(sc));
+        }));
+        // ---------------------------------------------------------------------
+        // comparing
+        // ---------------------------------------------------------------------
+        registerFunction("==", new BasicFunction("EQUAL", (sc, in) -> 
+        {
+            Object a = in[0].get(sc);
+            Object b = in[1].get(sc);
+            if(a == null || b == null)
+            {
+                return a == b ? 1 : 0;
+            }
+            else if(a instanceof String || b instanceof String)
+            {
+                return a.equals(b) ? 1 : 0;
+            }
+            return ((Number) a).doubleValue() == ((Number) b).doubleValue() ? 1 : 0;
+        }));
+        registerFunction("!=", new BasicFunction("NOTEQUAL", (sc, in) -> 
+        {
+            Object a = in[0].get(sc);
+            Object b = in[1].get(sc);
+            if(a == null || b == null)
+            {
+                return a != b ? 1 : 0;
+            }
+            else if(a instanceof String || b instanceof String)
+            {
+                return a.equals(b) ? 1 : 0;
+            }
+            return ((Number) a).doubleValue() != ((Number) b).doubleValue() ? 1 : 0;
+        }));
+        registerFunction(">", new BasicFunction("GREATER", (sc, in) -> 
+        {
+            return in[0].getDouble(sc) > in[1].getDouble(sc) ? 1 : 0;
+        }));
+        registerFunction(">=", new BasicFunction("GREATEREQUAL", (sc, in) -> 
+        {
+            return in[0].getDouble(sc) >= in[1].getDouble(sc) ? 1 : 0;
+        }));
+        registerFunction("<", new BasicFunction("SMALLER", (sc, in) -> 
+        {
+            return in[0].getDouble(sc) < in[1].getDouble(sc) ? 1 : 0;
+        }));
+        registerFunction("<=", new BasicFunction("SMALLEREQUAL", (sc, in) -> 
+        {
+            return in[0].getDouble(sc) <= in[1].getDouble(sc) ? 1 : 0;
+        }));
+        // ---------------------------------------------------------------------
+        // logical operators
+        // ---------------------------------------------------------------------
+        registerFunction("&&", new BasicFunction("AND", (sc, in) -> 
+        {
+            return (in[0].getDouble(sc) != 0 && in[1].getDouble(sc) != 0) ? 1 : 0;
+        }));
+        registerFunction("||", new BasicFunction("OR", (sc, in) -> 
+        {
+            return (in[0].getDouble(sc) != 0 || in[1].getDouble(sc) != 0) ? 1 : 0;
+        }));
+        // ---------------------------------------------------------------------
+        // bit stuff
+        // ---------------------------------------------------------------------
+        registerFunction(new BasicFunction("MOD", (sc, in) -> 
+        {
+            return in[0].getInt(sc) % in[1].getInt(sc);
+        }));
+        registerFunction("&", new BasicFunction("AND", (sc, in) -> 
+        {
+            return in[0].getInt(sc) & in[1].getInt(sc);
+        }));
+        registerFunction("|", new BasicFunction("OR", (sc, in) -> 
+        {
+            return in[0].getInt(sc) | in[1].getInt(sc);
+        }));
+        registerFunction("^", new BasicFunction("XOR", (sc, in) -> 
+        {
+            return in[0].getInt(sc) ^ in[1].getInt(sc);
+        }));
+        registerFunction("<<", new BasicFunction("SHIFTL", (sc, in) -> 
+        {
+            return in[0].getInt(sc) << in[1].getInt(sc);
+        }));
+        registerFunction(">>", new BasicFunction("SHIFTR", (sc, in) -> 
+        {
+            return in[0].getInt(sc) >> in[1].getInt(sc);
+        }));
+        // ---------------------------------------------------------------------
+        // basic instructions (variables and arrays)
+        // ---------------------------------------------------------------------
+        registerFunction("=", new BasicFunction("SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[1].get(sc));
+            return Void.TYPE;
+        }));
+        registerFunction("+=", new BasicFunction("ADD_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).add(in[1].getFraction(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction("-=", new BasicFunction("SUB_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).sub(in[1].getFraction(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction("*=", new BasicFunction("MUL_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).mul(in[1].getFraction(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction("/=", new BasicFunction("DIV_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).div(in[1].getFraction(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction("%=", new BasicFunction("MOD_SET", (sc, in) -> 
+        {
+            in[0].set(sc, new Fraction(in[0].getInt(sc) % in[1].getInt(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction("<<=", new BasicFunction("LEFT_SHIFT_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).leftShift(in[1].getInt(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction(">>=", new BasicFunction("RIGHT_SHIFT_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).rightShift(in[1].getInt(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction("&=", new BasicFunction("BIT_AND_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).and(in[1].getFraction(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction("^=", new BasicFunction("BIT_XOR_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).xor(in[1].getFraction(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction("|=", new BasicFunction("BIT_OR_SET", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getFraction(sc).or(in[1].getFraction(sc)));
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("DIM", (sc, in) -> 
+        {
+            for(InputProvider input : in)
+            {
+                ((DynamicArray) input).init(sc);
+            }
+            return Void.TYPE;
+        }));
+        registerAlias("DIM", "VAR");
+        registerFunction(new BasicFunction("SWAP", (sc, in) -> 
+        {
+            Object o = in[0].get(sc);
+            in[0].set(sc, in[1].get(sc));
+            in[1].set(sc, o);
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("INC", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getInt(sc) + (in.length > 1 ? in[1].getInt(sc) : 1));
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("DEC", (sc, in) -> 
+        {
+            in[0].set(sc, in[0].getInt(sc) - (in.length > 1 ? in[1].getInt(sc) : 1));
+            return Void.TYPE;
+        }));
+        // ---------------------------------------------------------------------
+        // basic instructions (control and branching)
+        // ---------------------------------------------------------------------
+        registerFunction(new BasicFunction("goto", (sc, in) -> 
+        {
+            sc.currentLine = sc.labels.get(in[0].getString(sc));
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("GOSUB", (sc, in) -> 
+        {
+            sc.returnStack.push(sc.currentLine);
+            sc.currentLine = sc.labels.get(in[0].getString(sc));
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("return", (sc, in) -> 
+        {
+            if(sc.returnStack.isEmpty())
+            {
+                sc.end();
+                sc.returnValue = in.length > 0 ? in[0].get(sc) : null;
+            }
+            else
+            {
+                sc.currentLine = sc.returnStack.pop();
+            }
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("if", (sc, in) -> 
+        {
+            int p = in[0].getInt(sc);
+            if(p == 0)
+            {
+                sc.currentLine += in[1].getInt(sc);
+            }
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("endif", (sc, in) -> 
+        {
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("for", (sc, in) -> 
+        {
+            // for(var, start, end, step)
+            Fraction start = in[1].getFraction(sc);
+            in[0].set(sc, start);           
+            if(start.compareTo(in[2].getFraction(sc)) > 0)
+            {
+                sc.currentLine += in[4].getInt(sc);
+            }
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("next", (sc, in) -> 
+        {
+            int line = sc.currentLine + in[0].getInt(sc);
+            InputProvider[] f = sc.code[line].getParameters();
+            // for(var, start, end, step)
+            Fraction current = f[0].getFraction(sc).add(f[3].getFraction(sc));
+            f[0].set(sc, current);
+            if(current.compareTo(f[2].getFraction(sc)) <= 0)
+            {
+                sc.currentLine = line;
+            }
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("while", (sc, in) -> 
+        {
+            if(in[0].getInt(sc) == 0)
+            {
+                sc.currentLine += in[1].getInt(sc);
+            }
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("wend", (sc, in) -> 
+        {
+            sc.currentLine += in[0].getInt(sc);
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("continue", (sc, in) -> 
+        {
+            sc.currentLine += in[0].getInt(sc);
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("break", (sc, in) -> 
+        {
+            sc.currentLine += in[0].getInt(sc);
+            return Void.TYPE;
+        }));
+        // ---------------------------------------------------------------------
+        // mathematics
+        // ---------------------------------------------------------------------
+        registerFunction(new BasicFunction("FLOOR", (sc, in) -> 
+        {
+            return (int) Math.floor(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("ROUND", (sc, in) -> 
+        {
+            return (int) Math.round(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("CEIL", (sc, in) -> 
+        {
+            return (int) Math.ceil(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("ABS", (sc, in) -> 
+        {
+            return in[0].getFraction(sc).abs();
+        }));
+        registerFunction(new BasicFunction("SGN", (sc, in) -> 
+        {
+            double d = in[0].getDouble(sc);
+            return d < 0 ? -1 : (d > 0 ? 1 : 0);
+        }));
+        /*registerFunction(new BasicFunction("MIN", (sc, in) -> 
+        {
+            if(in.length == 1)
+            {
+                return ((IMathOperation) in[0]).min(sc);
+            }
+            double min = Arrays.stream(in).mapToDouble(i -> i.getDouble(sc)).min().getAsDouble();
+            if(min == (int) min)
+            {
+                return (int) min;
+            }
+            return min;
+        }));
+        registerFunction(new BasicFunction("MAX", (sc, in) -> 
+        {
+            if(in.length == 1)
+            {
+                return ((IMathOperation) in[0]).max(sc);
+            }
+            double max = Arrays.stream(in).mapToDouble(i -> i.getDouble(sc)).max().getAsDouble();
+            if(max == (int) max)
+            {
+                return (int) max;
+            }
+            return max;
+        }));*/
+        registerFunction(new BasicFunction("RND", (sc, in) -> 
+        {
+            int seedId;
+            int max;
+            switch (in.length) 
+            {
+                case 1:
+                    seedId = 0;
+                    max = in[0].getInt(sc);
+                    break;
+                case 2:
+                    seedId = in[0].getInt(sc);
+                    max = in[1].getInt(sc);
+                    break;
+                default:
+                    throw new IllegalArgumentException("invalid number of arguments");
+            }
+            if(seedId < 0 || seedId > 7)
+            {
+                throw new IllegalArgumentException("seed id must be from 0 to 7");
+            }
+            return RND[seedId].nextInt(max);
+        }));
+        registerFunction(new BasicFunction("RNDF", (sc, in) -> 
+        {
+            int seedId = 0;
+            if(in.length > 0)
+            {
+                seedId = in[0].getInt(sc);
+            }
+            if(seedId < 0 || seedId > 7)
+            {
+                throw new IllegalArgumentException("seed id must be from 0 to 7");
+            }
+            return RND[seedId].nextDouble();
+        }));
+        registerFunction(new BasicFunction("RANDOMIZE", (sc, in) -> 
+        {
+            int seedId = in[0].getInt(sc);
+            if(seedId < 0 || seedId > 7)
+            {
+                throw new IllegalArgumentException("seed id must be from 0 to 7");
+            }
+            switch (in.length) 
+            {
+                case 1:
+                    RND[seedId] = new Random();
+                    break;
+                case 2:
+                    RND[seedId] = new Random(in[1].getInt(sc));
+                    break;
+                default:
+                    throw new IllegalArgumentException("invalid number of arguments");
+            }
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("SQR", (sc, in) -> 
+        {
+            return Math.sqrt(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("EXP", (sc, in) -> 
+        {
+            if(in.length == 0)
+            {
+                return Math.E;
+            }
+            return Math.exp(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("LOG", (sc, in) -> 
+        {
+            if(in.length >= 2)
+            {
+                return Math.log(in[0].getDouble(sc)) / Math.log(in[1].getDouble(sc));
+            }
+            return Math.log(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("POW", (sc, in) -> 
+        {
+            return Math.pow(in[0].getDouble(sc), in[1].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("PI", (sc, in) -> 
+        {
+            return Math.PI;
+        }));
+        registerFunction(new BasicFunction("RAD", (sc, in) -> 
+        {
+            return Math.toRadians(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("DEG", (sc, in) -> 
+        {
+            return Math.toDegrees(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("SIN", (sc, in) -> 
+        {
+            return Math.sin(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("COS", (sc, in) -> 
+        {
+            return Math.cos(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("TAN", (sc, in) -> 
+        {
+            return Math.tan(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("ASIN", (sc, in) -> 
+        {
+            return Math.asin(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("ACOS", (sc, in) -> 
+        {
+            return Math.acos(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("ATAN", (sc, in) -> 
+        {
+            if(in.length >= 2)
+            {
+                return Math.atan2(in[0].getDouble(sc), in[1].getDouble(sc));
+            }
+            return Math.atan(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("SINH", (sc, in) -> 
+        {
+            return Math.sinh(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("COSH", (sc, in) -> 
+        {
+            return Math.cosh(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("TANH", (sc, in) -> 
+        {
+            return Math.tanh(in[0].getDouble(sc));
+        }));
+        registerFunction(new BasicFunction("CLASSIFY", (sc, in) -> 
+        {
+            double d = in[0].getDouble(sc);
+            if(Double.isNaN(d))
+            {
+                return 2;
+            }
+            else if(Double.isInfinite(d))
+            {
+                return 1;
+            }
+            return 0;
+        }));
+        
+        
+        registerFunction(new BasicFunction("print", (sc, in) -> 
+        {
+            printMessage(Arrays.stream(in).map(s -> s.getString(sc)).collect(Collectors.joining()));
+            return Void.TYPE;
+        }));
+        registerFunction(new BasicFunction("TEST", (sc, in) -> 
+        {
+            return 1;
+        }));
+    }
+    
+    private static void printMessage(String message)
+    {
+        System.out.println(message);
+    }
+}

+ 57 - 0
src/me/hammerle/snuviscript/code/InputProvider.java

@@ -0,0 +1,57 @@
+package me.hammerle.snuviscript.code;
+
+import me.hammerle.snuviscript.variable.Variable;
+import me.hammerle.snuviscript.math.Fraction;
+
+public abstract class InputProvider 
+{
+    public Object get(Script sc)
+    {
+        throw new ClassCastException();
+    }
+    
+    public Fraction getFraction(Script sc)
+    {
+        throw new ClassCastException();
+    }
+    
+    public int getInt(Script sc)
+    {
+        throw new ClassCastException();
+    }
+    
+    public double getDouble(Script sc)
+    {
+        throw new ClassCastException();
+    }
+    
+    public String getString(Script sc)
+    {
+        throw new ClassCastException();
+    }
+    
+    public boolean getBoolean(Script sc)
+    {
+        throw new ClassCastException();
+    }
+    
+    public Variable getVariable(Script sc)
+    {
+        throw new ClassCastException();
+    }
+    
+    public void set(Script sc, Object o)
+    {
+        throw new ClassCastException();
+    }
+    
+    public Object getArray(Script sc)
+    {
+        return null;
+    }
+    
+    public boolean isArray(Script sc)
+    {
+        return false;
+    }
+}

+ 69 - 0
src/me/hammerle/snuviscript/code/Instruction.java

@@ -0,0 +1,69 @@
+package me.hammerle.snuviscript.code;
+
+public class Instruction
+{
+    private final int realLine;
+    private final byte layer;
+    
+    private final BasicFunction function;
+    private InputProvider[] input;
+    
+    public Instruction(int realLine, byte layer, BasicFunction function, InputProvider[] input)
+    {
+        this.realLine = realLine;
+        this.layer = layer;
+        this.function = function;
+        this.input = input;
+    }
+
+    public int getLayer() 
+    {
+        return layer;
+    }
+
+    public InputProvider[] getParameters() 
+    {
+        return input;
+    }
+    
+    @Override
+    public String toString() 
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(realLine);
+        
+        for(int j = sb.length(); j < 10; j++)
+        {
+            sb.insert(0, "0");
+        }
+        
+        sb.append(" | ");
+        
+        for(int j = 0; j < layer; j++)
+        {
+            sb.append("    ");
+        }
+        
+        sb.append(function.getName());
+        sb.append("(");
+        
+        for(InputProvider in : input)
+        {
+            sb.append(in);
+            sb.append(", ");
+        }
+        if(input.length > 0)
+        {
+            sb.delete(sb.length() - 2, sb.length());
+        }
+        
+        sb.append(")");
+        
+        return sb.toString();
+    }
+    
+    public void execute(Script sc)
+    {
+        function.execute(sc, input);
+    }
+}

+ 31 - 0
src/me/hammerle/snuviscript/code/JumpData.java

@@ -0,0 +1,31 @@
+package me.hammerle.snuviscript.code;
+
+import me.hammerle.snuviscript.code.InputProvider;
+import me.hammerle.snuviscript.code.Script;
+
+public class JumpData extends InputProvider
+{
+    private int jump;
+    
+    public JumpData(int jump)
+    {
+        this.jump = jump;
+    }
+
+    @Override
+    public int getInt(Script sc) 
+    {
+        return jump;
+    }
+
+    public void setRelativeJump(int jump) 
+    {
+        this.jump = jump - this.jump - 1;
+    }
+
+    @Override
+    public String toString() 
+    {
+        return "jump_" + jump;
+    }
+}

+ 82 - 0
src/me/hammerle/snuviscript/code/Script.java

@@ -0,0 +1,82 @@
+package me.hammerle.snuviscript.code;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+import me.hammerle.snuviscript.variable.Variable;
+
+public class Script 
+{
+    protected int currentLine;
+    protected Instruction[] code;
+    
+    protected final HashMap<String, Integer> labels;
+    protected final Stack<Integer> returnStack;
+    protected final Stack<HashMap<String, Variable>> localVars;
+    
+    protected Object returnValue;
+    protected final boolean subScript;
+    protected final String[] subScriptInput;
+    protected final HashMap<String, Script> subScripts;
+    
+    public Script(List<String> code)
+    {
+        this.subScriptInput = null;
+        this.subScripts = new HashMap<>();
+        this.labels = new HashMap<>();
+        this.returnStack = new Stack<>();
+        this.localVars = new Stack<>();
+        this.subScript = false;
+        this.currentLine = 0;
+        
+        this.code = Compiler.compile(this, code, labels, subScript, 0);
+        
+        /*System.out.println("__________________________________");
+        subScripts.forEach((k, v) -> 
+        {
+            System.out.println(k);
+            for(Instruction in : v.code)
+            {
+                System.out.println(in);
+            }
+            System.out.println("__________________________________");
+        });*/
+    }
+    
+    public Script(List<String> code, String[] subScriptInput, Script sc, int lineOffset)
+    {
+        this.subScriptInput = subScriptInput;
+        this.subScripts = sc.subScripts;
+        this.labels = new HashMap<>();
+        this.returnStack = new Stack<>();
+        this.localVars = sc.localVars;
+        this.subScript = true;
+        
+        this.code = Compiler.compile(this, code, labels, subScript, lineOffset);
+
+        this.currentLine = 0;
+    }
+    
+    public HashMap<String, Variable> getLocalVars()
+    {
+        return localVars.peek();
+    }
+    
+    public Object run()
+    {
+        int length = code.length;
+        returnValue = null;
+        while(currentLine < length)
+        {
+            //System.out.println("EXECUTE: " + code[currentLine]);
+            code[currentLine].execute(this);
+            currentLine++;
+        }
+        return returnValue;
+    }
+    
+    public void end()
+    {
+        currentLine = code.length;
+    }
+}

+ 50 - 0
src/me/hammerle/snuviscript/code/SignInverter.java

@@ -0,0 +1,50 @@
+package me.hammerle.snuviscript.code;
+
+import me.hammerle.snuviscript.code.Script;
+import me.hammerle.snuviscript.math.Fraction;
+
+public class SignInverter extends InputProvider
+{
+    private final InputProvider input;
+    
+    public SignInverter(InputProvider input)
+    {
+        this.input = input;
+    }
+    
+    @Override
+    public Object get(Script sc) 
+    {
+        return ((Fraction) input.get(sc)).invertSign();
+    }
+    
+    @Override
+    public Fraction getFraction(Script sc) 
+    {
+        return input.getFraction(sc).invertSign();
+    }
+    
+    @Override
+    public int getInt(Script sc) 
+    {
+        return input.getFraction(sc).invertSign().intValue();
+    }
+    
+    @Override
+    public double getDouble(Script sc) 
+    {
+        return input.getFraction(sc).invertSign().doubleValue();
+    }
+    
+    @Override
+    public String getString(Script sc) 
+    {
+        return String.valueOf(get(sc));
+    }
+    
+    @Override
+    public String toString() 
+    {
+        return "invertSign(" + input + ")";
+    }
+}

+ 229 - 0
src/me/hammerle/snuviscript/code/Syntax.java

@@ -0,0 +1,229 @@
+package me.hammerle.snuviscript.code;
+
+public enum Syntax
+{
+    UNKNOWN(" ", 0, 0),
+    MAYBE("", 0, 0),
+    MUL("*", 3),
+    DIV("/", 3),
+    MOD("%", 3),
+    ADD("+", 4),
+    SUB("-", 4),
+    UNARY_SUB(" ", 0, 1),
+    LEFT_SHIFT("<<", 6),
+    RIGHT_SHIFT(">>", 6),
+    SMALLER("<", 7),
+    SMALLER_EQUAL("<=", 7),
+    GREATER(">", 7),
+    GREATER_EQUAL(">=", 7),
+    EQUAL("==", 8),
+    NOT_EQUAL("!=", 8),
+    BIT_AND("&", 9),
+    BIT_XOR("^", 10),
+    BIT_OR("|", 11),
+    AND("&&", 12),
+    OR("||", 13),
+    SET("=", 15),
+    ADD_SET("+=", 15),
+    SUB_SET("-=", 15),
+    MUL_SET("*=", 15),
+    DIV_SET("/=", 15),
+    MOD_SET("%=", 15),
+    LEFT_SHIFT_SET("<<=", 15),
+    RIGHT_SHIFT_SET(">>=", 15),
+    BIT_AND_SET("&=", 15),
+    BIT_XOR_SET("^=", 15),
+    BIT_OR_SET("|=", 15);
+    
+    /*
+        LEFT_SHIFT_SET("<<=", 15),
+        RIGHT_SHIFT_SET(">>=", 15),
+    */
+
+    public static Syntax getSyntax(String s)
+    {   
+        int size = s.length();
+        if(size > 0)
+        {
+            switch(s.charAt(0))
+            {
+                case '*':
+                    if(size == 1)
+                    {
+                        return MUL;
+                    }
+                    else if(size == 2 && s.charAt(1) == '=')
+                    {
+                        return MUL_SET;
+                    }
+                    break;
+                case '/':
+                    if(size == 1)
+                    {
+                        return DIV;
+                    }
+                    else if(size == 2 && s.charAt(1) == '=')
+                    {
+                        return DIV_SET;
+                    }
+                    break;
+                case '+':
+                    if(size == 1)
+                    {
+                        return ADD;
+                    }
+                    else if(size == 2 && s.charAt(1) == '=')
+                    {
+                        return ADD_SET;
+                    }
+                    break;
+                case '-':
+                    if(size == 1)
+                    {
+                        return SUB;
+                    }
+                    else if(size == 2 && s.charAt(1) == '=')
+                    {
+                        return SUB_SET;
+                    }
+                    break;
+                case '^':
+                    if(size == 1)
+                    {
+                        return BIT_XOR;
+                    }
+                    else if(size == 2 && s.charAt(1) == '=')
+                    {
+                        return BIT_XOR_SET;
+                    }
+                    break;
+                case '<':
+                    if(size == 1)
+                    {
+                        return SMALLER;
+                    }
+                    else if(size == 2)
+                    {
+                        switch(s.charAt(1))
+                        {
+                            case '<': return LEFT_SHIFT;
+                            case '=': return SMALLER_EQUAL;
+                        }
+                    }
+                    else if(size == 3 && s.charAt(1) == '<' && s.charAt(2) == '=')
+                    {
+                        return LEFT_SHIFT_SET;
+                    }
+                    break;
+                case '>':
+                    if(size == 1)
+                    {
+                        return GREATER;
+                    }
+                    else if(size == 2)
+                    {
+                        switch(s.charAt(1))
+                        {
+                            case '>': return RIGHT_SHIFT;
+                            case '=': return GREATER_EQUAL;
+                        }
+                    }
+                    else if(size == 3 && s.charAt(1) == '>' && s.charAt(2) == '=')
+                    {
+                        return RIGHT_SHIFT_SET;
+                    }
+                    break;
+                case '!':
+                    if(size == 1)
+                    {
+                        return MAYBE;
+                    }
+                    else if(size == 2 && s.charAt(1) == '=')
+                    {
+                        return NOT_EQUAL;
+                    }
+                    break;
+                case '=':
+                    if(size == 1)
+                    {
+                        return SET;
+                    }
+                    else if(size == 2 && s.charAt(1) == '=')
+                    {
+                        return EQUAL;
+                    }
+                    break;
+                case '&':
+                    if(size == 1)
+                    {
+                        return BIT_AND;
+                    }
+                    else if(size == 2)
+                    {
+                        switch(s.charAt(1))
+                        {
+                            case '&': return AND;
+                            case '=': return BIT_AND_SET;
+                        }
+                    }
+                    break;
+                case '|':
+                    if(size == 1)
+                    {
+                        return BIT_OR;
+                    }
+                    else if(size == 2)
+                    {
+                        switch(s.charAt(1))
+                        {
+                            case '|': return OR;
+                            case '=': return BIT_OR_SET;
+                        }
+                    }
+                    break;
+                case '%':
+                    if(size == 1)
+                    {
+                        return MOD;
+                    }
+                    else if(size == 2 && s.charAt(1) == '=')
+                    {
+                        return MOD_SET;
+                    }
+                    break;
+            }
+        }
+        return UNKNOWN;
+    }
+    
+    private int weight;
+    private String function;
+    private byte pars;
+
+    Syntax(String function, int weight, int pars)
+    {
+        this.weight = weight;
+        this.function = function;
+        this.pars = (byte) pars;
+    }   
+    
+    Syntax(String function, int weight)
+    {
+        this(function, weight, 2);
+    }  
+
+    public String getFunction() 
+    {
+        return function;
+    }
+
+    public int getWeight() 
+    {
+        return weight;
+    }
+    
+    public byte getParameters()
+    {
+        return pars;
+    }
+}

+ 35 - 0
src/me/hammerle/snuviscript/constants/ConstantBoolean.java

@@ -0,0 +1,35 @@
+package me.hammerle.snuviscript.constants;
+
+import me.hammerle.snuviscript.code.InputProvider;
+import me.hammerle.snuviscript.code.Script;
+
+public class ConstantBoolean extends InputProvider
+{
+    public static final ConstantBoolean TRUE = new ConstantBoolean(true);
+    public static final ConstantBoolean FALSE = new ConstantBoolean(false);
+    
+    private final boolean b;
+    
+    private ConstantBoolean(boolean b)
+    {
+        this.b = b;
+    }
+    
+    @Override
+    public Object get(Script sc)
+    {
+        return b;
+    }
+    
+    @Override
+    public String getString(Script sc)
+    {
+        return String.valueOf(b);
+    }
+    
+    @Override
+    public boolean getBoolean(Script sc)
+    {
+        return b;
+    }
+}

+ 51 - 0
src/me/hammerle/snuviscript/constants/ConstantFraction.java

@@ -0,0 +1,51 @@
+package me.hammerle.snuviscript.constants;
+
+import me.hammerle.snuviscript.code.InputProvider;
+import me.hammerle.snuviscript.code.Script;
+import me.hammerle.snuviscript.math.Fraction;
+
+public class ConstantFraction extends InputProvider
+{
+    private final Fraction f;
+    
+    public ConstantFraction(Fraction f)
+    {
+        this.f = f;
+    }
+    
+    @Override
+    public Object get(Script sc)
+    {
+        return f;
+    }
+    
+    @Override
+    public Fraction getFraction(Script sc)
+    {
+        return f;
+    }
+    
+    @Override
+    public int getInt(Script sc)
+    {
+        return f.intValue();
+    }
+    
+    @Override
+    public double getDouble(Script sc)
+    {
+        return f.doubleValue();
+    }
+    
+    @Override
+    public String getString(Script sc)
+    {
+        return String.valueOf(f);
+    }
+
+    @Override
+    public String toString() 
+    {
+        return String.valueOf(f);
+    }
+}

+ 45 - 0
src/me/hammerle/snuviscript/constants/ConstantNull.java

@@ -0,0 +1,45 @@
+package me.hammerle.snuviscript.constants;
+
+import me.hammerle.snuviscript.code.InputProvider;
+import me.hammerle.snuviscript.code.Script;
+import me.hammerle.snuviscript.variable.Variable;
+import me.hammerle.snuviscript.math.Fraction;
+
+public class ConstantNull extends InputProvider
+{
+    public static final ConstantNull NULL = new ConstantNull();
+    
+    private ConstantNull()
+    {
+    }
+    
+    @Override
+    public Object get(Script sc)
+    {
+        return null;
+    }
+    
+    @Override
+    public Fraction getFraction(Script sc)
+    {
+        return null;
+    }
+    
+    @Override
+    public String getString(Script sc)
+    {
+        return "null";
+    }   
+    
+    @Override
+    public Variable getVariable(Script sc)
+    {
+        return null;
+    }
+    
+    @Override
+    public Object getArray(Script sc)
+    {
+        return null;
+    }
+}

+ 32 - 0
src/me/hammerle/snuviscript/constants/ConstantString.java

@@ -0,0 +1,32 @@
+package me.hammerle.snuviscript.constants;
+
+import me.hammerle.snuviscript.code.InputProvider;
+import me.hammerle.snuviscript.code.Script;
+
+public class ConstantString extends InputProvider
+{
+    private final String s;
+    
+    public ConstantString(String s)
+    {
+        this.s = s;
+    }
+    
+    @Override
+    public Object get(Script sc) 
+    {
+        return s;
+    }
+    
+    @Override
+    public String getString(Script sc) 
+    {
+        return s;
+    }
+
+    @Override
+    public String toString() 
+    {
+        return "\"" + s + "\"";
+    }
+}

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

@@ -0,0 +1,9 @@
+package me.hammerle.snuviscript.exceptions;
+
+public class PreScriptException extends RuntimeException
+{
+    public PreScriptException(String message, int line) 
+    {
+        super(message + " - line " + line);
+    }
+}

+ 695 - 0
src/me/hammerle/snuviscript/math/Fraction.java

@@ -0,0 +1,695 @@
+package me.hammerle.snuviscript.math;
+
+import java.util.TreeSet;
+
+public final class Fraction extends Number implements Comparable<Fraction>
+{
+    private final long numerator;
+    private final long denominator;
+    
+    public Fraction(long numerator, long denominator)
+    {
+        if(denominator == 0)
+        {
+            throw new ArithmeticException();
+        }
+        
+        if(denominator != 1)
+        {
+            long divisor = getGreatestCommonDivisor(numerator, denominator);
+            if(divisor != 1)
+            {
+                denominator /= divisor;
+                numerator /= divisor;
+            }
+        }
+        
+        // short fraction
+        if(denominator < 0)
+        {
+            this.denominator = -denominator;
+            this.numerator = -numerator;
+        }
+        else
+        {
+            this.denominator = denominator;
+            this.numerator = numerator;
+        }
+    }
+    
+    public Fraction(long numerator)
+    {
+        this(numerator, 1);
+    }
+    
+    private static final long DOUBLE_FACTOR = 10000000000l;
+    
+    public static Fraction fromDouble(double d)
+    {
+        if(d == (long) d)
+        {
+            return new Fraction((long) d);
+        }
+        return new Fraction(Math.round(d * DOUBLE_FACTOR), DOUBLE_FACTOR);
+    }
+    
+    // -------------------------------------------------------------------------
+    // constants, chain fractions
+    // ------------------------------------------------------------------------- 
+    
+    public static final Fraction PI = Fraction.getChainFraction(
+              //3,7,15,1,292,1,1,1,2,1,3,1,14,2,1,1,2,2,2,2,1,84,2,1,1,15,3,13,1,4,2,6,6
+                3,7,15,1,292,1,1,1,2,1,3,1,14,2,1);
+    public static final Fraction E = Fraction.getChainFraction(
+              //2,1,2,1,1,4,1,1,6,1,1,8,1,1,10,1,1,12,1,1,14,1,1,16,...
+                2,1,2,1,1,4,1,1,6,1,1,8,1,1,10,1,1,12,1,1,14);
+    
+    public static Fraction getChainFraction(int... ints)
+    {
+        if(ints.length == 1)
+        {
+            return new Fraction(ints[0]);
+        }
+        return new Fraction(ints[0]).add(getChainFractionPart(1, ints));
+    }
+    
+    private static Fraction getChainFractionPart(int index, int... ints)
+    {
+        if(index + 1 == ints.length)
+        {
+            return new Fraction(1, ints[index]);
+        }
+        return new Fraction(ints[index]).add(getChainFractionPart(index + 1, ints)).invert();
+    }
+    
+    // -------------------------------------------------------------------------
+    // basic calculating
+    // ------------------------------------------------------------------------- 
+    
+    public Fraction add(Fraction f)
+    {
+        if(denominator == f.denominator)
+        {
+            return new Fraction(Math.addExact(numerator, f.numerator), denominator);
+        }
+        else
+        {
+            long l = getLeastCommonMultiple(denominator, f.denominator);
+            return new Fraction(Math.addExact(Math.multiplyExact(numerator, l / denominator),
+                    Math.multiplyExact(f.numerator, l / f.denominator)), l);
+        }
+    }
+    
+    public Fraction mul(Fraction f)
+    {
+        return new Fraction(Math.multiplyExact(numerator, f.numerator), 
+                Math.multiplyExact(denominator, f.denominator));
+    }
+    
+    public Fraction sub(Fraction f)
+    {
+        return add(f.invertSign());
+    }
+    
+    public Fraction div(Fraction f)
+    {
+        return mul(f.invert());
+    }
+    
+    // -------------------------------------------------------------------------
+    // roots, power
+    // ------------------------------------------------------------------------- 
+    
+    private static long power(long l, long power)
+    {
+        if(power == 1)
+        {
+            return l;
+        }
+        else if(power == 2)
+        {
+            return l * l;
+        }
+        long factor = 1;
+        if(l < 0)
+        {
+            factor = (power & 1) == 1 ? -1 : 1;
+            l = -l;
+        }
+        long prod = 1;
+        while(power > 0)
+        {
+            if((power & 1) == 1)
+            {
+                prod = Math.multiplyExact(prod, l);
+            }
+            power = power >> 1;
+            l = Math.multiplyExact(l, l);
+        }
+        if(factor == -1)
+        {
+            return -prod;
+        }
+        return prod;
+    }
+    
+    private static long rootOfLong(long l, long root)
+    {
+        if(l == 0 || l == 1)
+        {
+            return l;
+        }
+        try
+        {
+            TreeSet<Long> tree = new TreeSet<>();
+            long currentValue = l >> 1; // taking half as start value
+            long an = currentValue;
+            do
+            {
+                tree.add(currentValue);
+                currentValue = currentValue - (currentValue / root) + an / power(currentValue, root - 1); 
+            }
+            while(!tree.contains(currentValue));
+            return currentValue;
+        }
+        catch(ArithmeticException ex)
+        {
+            return 0;
+        } 
+    }
+    
+    private Fraction root(long root)
+    {
+        if(root == 1)
+        {
+            return this.copy();
+        }
+        // Getting nice start value
+        Fraction currentValue;
+        Fraction n = new Fraction(root);
+        Fraction an = this.div(n);
+
+        Fraction newFraction = new Fraction(rootOfLong(numerator, root), rootOfLong(denominator, root));
+        root--;
+        try
+        {
+            do
+            {
+                currentValue = newFraction;
+                newFraction = currentValue.sub(currentValue.div(n)).add(an.div(currentValue.power(root)));              
+            }
+            while(!newFraction.equals(currentValue));
+        }
+        catch(ArithmeticException ex)
+        {
+        }
+        return newFraction;
+    }
+    
+    public Fraction power(Fraction f)
+    {
+        if(numerator < 0 && f.denominator != 1)
+        {
+            throw new ArithmeticException("root of negative fraction");
+        }
+        try
+        {
+            if(f.numerator == 0)
+            {
+                return new Fraction(1);
+            }
+            else if(f.numerator < 0)
+            {
+                return this.invert().power(-f.numerator).root(f.denominator);
+            }
+            return this.power(f.numerator).root(f.denominator);
+        }
+        catch(ArithmeticException ex)
+        {
+            return fromDouble(Math.pow(this.doubleValue(), f.doubleValue()));
+        }
+    }
+    
+    private Fraction power(long p)
+    {
+        if(p < 0)
+        {
+            p = -p;
+            invert();
+        }
+        else if(p == 1)
+        {
+            return this.copy();
+        }
+        long prodn = 1;
+        long prodd = 1;
+        long n = numerator;
+        long d = denominator;
+        while(p > 0)
+        {
+            if((p & 1) == 1)
+            {
+                prodn = Math.multiplyExact(prodn, n);
+                prodd = Math.multiplyExact(prodd, d);
+            }
+            p = p >> 1;
+            n = Math.multiplyExact(n, n);
+            d = Math.multiplyExact(d, d);
+        }
+        return new Fraction(prodn, prodd);
+    }
+    
+    // -------------------------------------------------------------------------
+    // inverting
+    // -------------------------------------------------------------------------
+    
+    public Fraction invertSign()
+    {
+        return new Fraction(-numerator, denominator);
+    }
+    
+    public Fraction invert()
+    {
+        if(numerator == 0)
+        {
+            throw new ArithmeticException();
+        }
+        else if(numerator < 0)
+        {
+            return new Fraction(-denominator, -numerator);
+        }
+        return new Fraction(denominator, numerator);
+    }
+    
+    public boolean isNegative()
+    {
+        return numerator < 0;
+    }
+    
+    public boolean isLong()
+    {
+        return denominator == 1;
+    }
+    
+    // -------------------------------------------------------------------------
+    // functions from math library
+    // -------------------------------------------------------------------------
+    
+    public Fraction abs()
+    {
+        return new Fraction(Math.abs(numerator), denominator);
+    }
+    
+    public Fraction acos()
+    {
+        return Fraction.fromDouble(Math.acos(doubleValue()));
+    }
+    
+    public Fraction asin()
+    {
+        return Fraction.fromDouble(Math.asin(doubleValue()));
+    }
+    
+    public Fraction atan()
+    {
+        return Fraction.fromDouble(Math.atan(doubleValue()));
+    }
+    
+    public Fraction atan2(Fraction f)
+    {
+        return Fraction.fromDouble(Math.atan2(doubleValue(), f.doubleValue()));
+    }
+    
+    public Fraction cbrt()
+    {
+        return this.power(new Fraction(1, 3));
+    }
+    
+    public Fraction ceil()
+    {
+        return Fraction.fromDouble(Math.ceil(doubleValue()));
+    }
+    
+    public Fraction cos()
+    {
+        return Fraction.fromDouble(Math.cos(doubleValue()));
+    }
+    
+    public Fraction cosh()
+    {
+        return Fraction.fromDouble(Math.cosh(doubleValue()));
+    }
+    
+    public Fraction floor()
+    {
+        return Fraction.fromDouble(Math.floor(doubleValue()));
+    }
+    
+    public Fraction log()
+    {
+        return Fraction.fromDouble(Math.log(doubleValue()));
+    }
+    
+    public Fraction log10()
+    {
+        return Fraction.fromDouble(Math.log10(doubleValue()));
+    }
+    
+    public Fraction log1p()
+    {
+        return Fraction.fromDouble(Math.log1p(doubleValue()));
+    }
+    
+    public Fraction max(Fraction f)
+    {
+        if(this.compareTo(f) < 0)
+        {
+            return f;
+        }
+        return this;
+    }
+    
+    public Fraction min(Fraction f)
+    {
+        if(this.compareTo(f) > 0)
+        {
+            return f;
+        }
+        return this;
+    }
+    
+    public Fraction rint()
+    {
+        return Fraction.fromDouble(Math.rint(doubleValue()));
+    }
+    
+    public Fraction round()
+    {
+        return Fraction.fromDouble(Math.round(doubleValue()));
+    }
+    
+    public Fraction round(int times)
+    {
+        if(times < 0)
+        {
+            throw new IllegalArgumentException("a positive number of decimal points is needed");
+        }
+        int factor = 1;
+        while(times > 0)
+        {
+            factor *= 10;
+            times--;
+        }
+        double d = doubleValue() * factor;
+        return new Fraction(Math.round(d), factor);
+    }
+    
+    public Fraction signum()
+    {
+        if(numerator < 0)
+        {
+            return new Fraction(-1);
+        }
+        return new Fraction(1);
+    }
+    
+    public Fraction sin()
+    {
+        return Fraction.fromDouble(Math.sin(doubleValue()));
+    }
+    
+    public Fraction sinh()
+    {
+        return Fraction.fromDouble(Math.sinh(doubleValue()));
+    }
+    
+    public Fraction tan()
+    {
+        return Fraction.fromDouble(Math.tan(doubleValue()));
+    }
+    
+    public Fraction tanh()
+    {
+        return Fraction.fromDouble(Math.tanh(doubleValue()));
+    }
+    
+    public Fraction toDegrees()
+    {
+        return Fraction.fromDouble(Math.toDegrees(doubleValue()));
+    }
+    
+    public Fraction toRadians()
+    {
+        return Fraction.fromDouble(Math.toRadians(doubleValue()));
+    }
+    
+    // -------------------------------------------------------------------------
+    // simplifying
+    // ------------------------------------------------------------------------- 
+    
+    public Fraction simplify(int times)
+    {
+        if(denominator == 1)
+        {
+            return this.copy();
+        }
+        long d = denominator;
+        long n = numerator;
+        int switcher = -1;
+        for(int i = 0; i < times; i++)
+        {
+            if(switcher == -1 && d == 1)
+            {
+                d = (d & 1) == 1 ? d + 1: d;
+                n = (n & 1) == 1 ? n + 1 : n;
+                switcher = -1;
+            }
+            else if(switcher == 1 && d == -1)
+            {
+                d = (d & 1) == 1 ? d - 1: d;
+                n = (n & 1) == 1 ? n - 1 : n;
+                switcher = 1;
+            }
+            else
+            {
+                d = (d & 1) == 1 ? d + switcher: d;
+                n = (n & 1) == 1 ? n + switcher : n;
+                switcher = -switcher;
+            }
+            
+            if(d != 1)
+            {
+                long divisor = getGreatestCommonDivisor(n, d);
+                //System.out.println("DIV: " + divisor);
+                if(divisor != 1)
+                {
+                    d /= divisor;
+                    n /= divisor;
+                }
+            }
+        }
+        return new Fraction(n, d);
+    }
+    
+    private long getGreatestCommonDivisor(long i, long n)
+    {
+        if(i == 0)
+        {
+            return n;
+        }
+        if(i < 0)
+        {
+            i = -i;
+        }
+        if(n < 0)
+        {
+            n = -n;
+        }
+        long helper;
+        while(true)
+        {
+            if(i < n)
+            {
+                helper = i;
+                i = n;
+                n = helper;
+            }
+            i = i % n;
+            if(i == 0)
+            {
+                return n;
+            }
+        }
+    }
+    
+    private long getLeastCommonMultiple(long i, long n)
+    {
+        return Math.abs(Math.multiplyExact(i, n)) / getGreatestCommonDivisor(i, n);
+    }
+    
+    // -------------------------------------------------------------------------
+    // basic stuff
+    // ------------------------------------------------------------------------- 
+    
+    @Override
+    public String toString() 
+    {
+        if(denominator == 1)
+        {
+            return String.valueOf(numerator);
+        }
+        return numerator + " / " + denominator;
+    }
+
+    private Fraction copy()
+    {
+        return new Fraction(numerator, denominator);
+    }
+
+    @Override
+    public boolean equals(Object o) 
+    {
+        if(o == null)
+        {
+            return false;
+        }
+        else if(o.getClass() != Fraction.class)
+        {
+            return false;
+        }
+        Fraction f = (Fraction) o;
+        return numerator == f.numerator && denominator == f.denominator;
+    }
+
+    @Override
+    public int hashCode() 
+    {
+        int hash = 3;
+        hash = 97 * hash + (int) (this.numerator ^ (this.numerator >>> 32));
+        hash = 97 * hash + (int) (this.denominator ^ (this.denominator >>> 32));
+        return hash;
+    }
+
+    @Override
+    public int compareTo(Fraction f) 
+    {
+        if(f.numerator < 0 && numerator >= 0)
+        {
+            return 1;
+        }
+        else if(f.numerator >= 0 && numerator < 0)
+        {
+            return -1;
+        }
+        else
+        {
+            long i = f.sub(this).numerator;
+            if(i == 0)
+            {
+                return 0;
+            }
+            else if(i < 0)
+            {
+                return 1;
+            }
+            else
+            {
+                return -1;
+            }
+        }
+    }
+    
+    // -------------------------------------------------------------------------
+    // bit stuff
+    // -------------------------------------------------------------------------
+    
+    private void noFraction()
+    {
+        if(denominator != 1 && numerator != 0)
+        {
+            throw new UnsupportedOperationException("the number must not be a fraction");
+        }
+    }
+    
+    public Fraction rightShift(int times)
+    {
+        noFraction();
+        return new Fraction(numerator >> times);
+    }
+    
+    public Fraction leftShift(int times)
+    {
+        noFraction();
+        return new Fraction(numerator << times);
+    }
+    
+    public Fraction and(Fraction f)
+    {
+        noFraction();
+        return new Fraction(numerator & f.numerator);
+    }
+    
+    public Fraction or(Fraction f)
+    {
+        noFraction();
+        return new Fraction(numerator | f.numerator);
+    }
+    
+    public Fraction xor(Fraction f)
+    {
+        noFraction();
+        return new Fraction(numerator ^ f.numerator);
+    }
+    
+    public Fraction invertBits()
+    {
+        noFraction();
+        return new Fraction(~numerator);
+    }
+
+    public Fraction setBit(int n)
+    {
+        noFraction();
+        return new Fraction(numerator | 1 << n);
+    }
+    
+    public Fraction unsetBit(int n)
+    {
+        noFraction();
+        return new Fraction(numerator & ~(1 << n));
+    }   
+    
+    public boolean getBit(int n)
+    {
+        noFraction();
+        return (numerator & (1 << n)) != 0;
+    }
+    
+    // -------------------------------------------------------------------------
+    // number stuff
+    // ------------------------------------------------------------------------- 
+
+    @Override
+    public int intValue() 
+    {
+        return (int) longValue();
+    }
+
+    @Override
+    public long longValue()
+    {
+        return numerator / denominator;
+    }
+
+    @Override
+    public float floatValue() 
+    {
+        return (float) doubleValue();
+    }
+
+    @Override
+    public double doubleValue()
+    {
+        return numerator / (double) denominator;
+    }
+}

+ 24 - 0
src/me/hammerle/snuviscript/variable/ArrayVariable.java

@@ -0,0 +1,24 @@
+package me.hammerle.snuviscript.variable;
+
+import me.hammerle.snuviscript.code.DataUtils;
+import me.hammerle.snuviscript.code.Script;
+
+public class ArrayVariable extends Variable
+{
+    public ArrayVariable(String name) 
+    {
+        super(name);
+    }
+    
+    @Override
+    public String getString(Script sc) 
+    {
+        return DataUtils.getArrayString(get(sc));
+    }
+
+    @Override
+    public boolean isArray(Script sc) 
+    {
+        return true;
+    }
+}

+ 45 - 0
src/me/hammerle/snuviscript/variable/LocalArrayVariable.java

@@ -0,0 +1,45 @@
+package me.hammerle.snuviscript.variable;
+
+import java.util.HashMap;
+import me.hammerle.snuviscript.code.DataUtils;
+import me.hammerle.snuviscript.code.Script;
+
+public class LocalArrayVariable extends LocalVariable
+{
+    public LocalArrayVariable(String name) 
+    {
+        super(name);
+    }
+    
+    @Override
+    public String getString(Script sc) 
+    {
+        return DataUtils.getArrayString(get(sc));
+    }
+    
+    @Override
+    public Variable getVariable(Script sc) 
+    {
+        HashMap<String, Variable> map = sc.getLocalVars();
+        Variable v = map.get(name);
+        if(v != null)
+        {
+            return v;
+        }
+        v = new ArrayVariable(name);
+        map.put(name, v);
+        return v;
+    }
+    
+    @Override
+    public String toString() 
+    {
+        return name + "#L[]";
+    }
+    
+    @Override
+    public boolean isArray(Script sc) 
+    {
+        return true;
+    }
+}

+ 81 - 0
src/me/hammerle/snuviscript/variable/LocalVariable.java

@@ -0,0 +1,81 @@
+package me.hammerle.snuviscript.variable;
+
+import java.util.HashMap;
+import me.hammerle.snuviscript.code.Script;
+import me.hammerle.snuviscript.math.Fraction;
+
+public class LocalVariable extends Variable
+{
+    public LocalVariable(String name)
+    {
+        super(name);
+    }
+    
+    @Override
+    public String toString() 
+    {
+        return name + "#L";
+    }
+    
+    @Override
+    public Object get(Script sc) 
+    {
+        return getVariable(sc).get(sc);
+    }
+    
+    @Override
+    public Fraction getFraction(Script sc)
+    {
+        return getVariable(sc).getFraction(sc);
+    }
+    
+    @Override
+    public int getInt(Script sc)
+    {
+        return getVariable(sc).getInt(sc);
+    }
+    
+    @Override
+    public double getDouble(Script sc)
+    {
+        return getVariable(sc).getDouble(sc);
+    }
+    
+    @Override
+    public String getString(Script sc)
+    {
+        return getVariable(sc).getString(sc);
+    }
+    
+    @Override
+    public boolean getBoolean(Script sc)
+    {
+        return getVariable(sc).getBoolean(sc);
+    }
+    
+    @Override
+    public Variable getVariable(Script sc)
+    {
+        HashMap<String, Variable> map = sc.getLocalVars();
+        Variable v = map.get(name);
+        if(v != null)
+        {
+            return v;
+        }
+        v = new Variable(name);
+        map.put(name, v);
+        return v;
+    }
+    
+    @Override
+    public void set(Script sc, Object o) 
+    {
+        getVariable(sc).set(sc, o);
+    }
+    
+    @Override
+    public Object getArray(Script sc)
+    {
+        return getVariable(sc).getArray(sc);
+    }
+}

+ 82 - 0
src/me/hammerle/snuviscript/variable/Variable.java

@@ -0,0 +1,82 @@
+package me.hammerle.snuviscript.variable;
+
+import me.hammerle.snuviscript.code.Script;
+import me.hammerle.snuviscript.code.InputProvider;
+import me.hammerle.snuviscript.math.Fraction;
+
+public class Variable extends InputProvider
+{
+    protected final String name;
+    private Object o;
+    
+    public Variable(String name)
+    {
+        this.name = name;
+        this.o = null;
+    }
+
+    public String getName() 
+    {
+        return name;
+    }
+    
+    @Override
+    public String toString() 
+    {
+        return name;
+    }
+    
+    @Override
+    public Object get(Script sc)
+    {
+        return o;
+    }
+    
+    @Override
+    public Fraction getFraction(Script sc)
+    {
+        return (Fraction) o;
+    }
+    
+    @Override
+    public int getInt(Script sc)
+    {
+        return getFraction(sc).intValue();
+    }
+    
+    @Override
+    public double getDouble(Script sc)
+    {
+        return getFraction(sc).doubleValue();
+    }
+    
+    @Override
+    public String getString(Script sc)
+    {
+        return String.valueOf(o);
+    }
+    
+    @Override
+    public boolean getBoolean(Script sc)
+    {
+        return (boolean) o;
+    }
+    
+    @Override
+    public Variable getVariable(Script sc) 
+    {
+        return this;
+    }
+    
+    @Override
+    public void set(Script sc, Object o)
+    {
+        this.o = o;
+    }
+    
+    @Override
+    public Object getArray(Script sc)
+    {
+        return o;
+    }
+}

+ 16 - 0
test.sbasic

@@ -0,0 +1,16 @@
+function fac(n) 
+{ 
+    if(n == 1)
+    {
+        return 1;
+    }
+    return n * fac(n - 1);
+}
+
+function test() 
+{ 
+    print("Hallo");
+}
+
+test();
+print(fac(6));