# [Prelude](http://esolangs.org/wiki/Prelude)

Maybe I can increase Prelude adoption a bit if people can just play around with it in their browsers, so here is a JavaScript interpreter. It's based on [Sp3000's nifty Brainfuck interpreter](http://codegolf.stackexchange.com/a/40091/8478), whose killer feature is interruptible execution and an optional time out.

<!-- begin snippet: js hide: false -->

<!-- language: lang-js -->

    var ITERS_PER_SEC = 100000;
    var TIMEOUT_MILLISECS = 5000;
    var ERROR_LOOP_MISMATCH = "Mismatched parentheses";
    var ERROR_LOOP_MULTI = "Multiple parentheses in the same column";
    var ERROR_TIMEOUT = "Timeout";
    var ERROR_INTERRUPT = "Interrupted by user";

    var code, input, timeout, num_input, num_output, loop_stack, loop_map;
    var running, start_time, code_ptr, width, input_ptr, voices, iterations;

    function clear_output() {
        document.getElementById("output").value = "";
        document.getElementById("stderr").innerHTML = "";
    }

    function stop() {
        running = false;
        document.getElementById("run").disabled = false;
        document.getElementById("stop").disabled = true;
        document.getElementById("clear").disabled = false;
        document.getElementById("num_input").disabled = false;
        document.getElementById("num_output").disabled = false;
        document.getElementById("timeout").disabled = false;
    }

    function interrupt() {
        error(ERROR_INTERRUPT);
    }

    function error(msg) {
        document.getElementById("stderr").innerHTML = msg;
        stop();
    }

    function run() {
        clear_output();

        document.getElementById("run").disabled = true;
        document.getElementById("stop").disabled = false;
        document.getElementById("clear").disabled = true;
        document.getElementById("num_input").disabled = false;
        document.getElementById("num_output").disabled = false;
        document.getElementById("timeout").disabled = false;

        code = document.getElementById("code").value;
        input = document.getElementById("input").value;
        num_input = document.getElementById("num_input").checked;
        num_output = document.getElementById("num_output").checked;
        timeout = document.getElementById("timeout").checked;

        loop_stack = [];
        loop_map = {};

        if (num_input) {
            input = input.match(/-?\d+/g).map(function (n) {
                return +n;
            });
        } else {
            input = input.split("").map(function (s) {
                return s.charCodeAt(0);
            });
        }

        code = code.split("\n");
        width = 0;
        for (var i = 0; i < code.length; ++i)
            if (code[i].length > width) 
                width = code[i].length;
        console.log(code);
        console.log(width);
        for (var i = 0; i < width; ++i) {
            var hasParens = false;
            for (var j = 0; j < code.length; ++j) {
                var char = code[j][i];
                if (char == "(" || char == ")") {
                    if (hasParens) {
                        error(ERROR_LOOP_MULTI);
                        return;
                    }

                    hasParens = true;
                    if (char == "(") loop_stack.push({
                        x: i,
                        y: j
                    });
                    else {
                        if (loop_stack.length == 0) {
                            error(ERROR_LOOP_MISMATCH);
                            return;
                        } else {
                            var last_parens = loop_stack.pop();
                            loop_map[last_parens.x] = {
                                x: i,
                                y: last_parens.y
                            };
                            loop_map[i] = last_parens;
                        }
                    }
                }
            }
            console.log(i);
            console.log(loop_stack);
        }

        if (loop_stack.length > 0) {
            error(ERROR_LOOP_MISMATCH);
            return;
        }

        console.log(loop_map);

        running = true;
        start_time = Date.now();
        code_ptr = 0;
        input_ptr = 0;
        voices = code.map(function () {
            return [];
        });
        iterations = 0;

        prelude_iter(1);
    }

    function prelude_iter(niters) {
        if (code_ptr >= width || !running) {
            stop();
            return;
        }

        console.log(voices);
        var iter_start_time = Date.now();

        for (var i = 0; i < niters; ++i) {
            var previousTops = voices.map(function(v) { return v[v.length-1] || 0; });
            
            for (var j = 0; j < code.length; ++j) {
                switch (code[j][code_ptr]) {
                    case "0":
                    case "1":
                    case "2":
                    case "3":
                    case "4":
                    case "5":
                    case "6":
                    case "7":
                    case "8":
                    case "9":
                        voices[j].push(+code[j][code_ptr]);
                        break;
                        
                    case "+":
                        var x = voices[j].pop() || 0;
                        var y = voices[j].pop() || 0;
                        voices[j].push(x + y);
                        break;

                    case "-":
                        var x = voices[j].pop() || 0;
                        var y = voices[j].pop() || 0;
                        voices[j].push(y - x);
                        break;

                    case "v":
                        voices[j].push(previousTops[j == code.length-1 ? 0 : j + 1]);
                        break;
                        
                    case "^":
                        voices[j].push(previousTops[j == 0 ? code.length-1 : j - 1]);
                        break;
                        
                    case "#":
                        voices[j].pop();
                        break;

                    case "!":
                        var value = voices[j].pop() || 0; 
                        document.getElementById("output").value += num_output ? value+"\n" : String.fromCharCode(value);
                        break;

                    case "?":
                        if (input_ptr >= input.length) {
                            voices[j].push(0);
                        } else {
                            voices[j].push(input[input_ptr]);
                            ++input_ptr;
                        }
                        break;
                }
            }

            if (loop_map[code_ptr])
            {
                var other = loop_map[code_ptr];
                code_ptr = previousTops[other.y] ? Math.min(code_ptr, other.x) : Math.max(code_ptr, other.x);
            }

            ++code_ptr;
            ++iterations;

            if (timeout && Date.now() - start_time > TIMEOUT_MILLISECS) {
                error(ERROR_TIMEOUT);
                return;
            }
        }

        setTimeout(function () {
            prelude_iter(ITERS_PER_SEC * (Date.now() - iter_start_time) / 1000)
        }, 0);
    }

<!-- language: lang-html -->

    <div style="font-size:12px;font-family:Verdana, Geneva, sans-serif;">Code:
        <br>
        <textarea id="code" rows="4" style="overflow:scroll;overflow-x:hidden;width:90%;">1(  #^    #^^   (#^+!
    6 9+ 05+5+^#^+! #^ ^+!
       (((1- )#^##)^^+   )7-!)1433545514232323344949145353495314235494040404232323013334492349532333344953493435343584233475234501333449530423232349495323232323495323230494)</textarea>
        <br>Input:
        <br>
        <textarea id="input" rows="2" style="overflow:scroll;overflow-x:hidden;width:90%;">5a-13 11 2e3</textarea>
        <p>Numeric Input:
            <input id="num_input" type="checkbox">&nbsp; Numeric Output:
            <input id="num_output" type="checkbox">&nbsp; Timeout:
            <input id="timeout" type="checkbox" checked="checked">&nbsp;
            <br>
            <br>
            <input id="run" type="button" value="Run" onclick="run()">
            <input id="stop" type="button" value="Stop" onclick="interrupt()" disabled="disabled">
            <input id="clear" type="button" value="Clear" onclick="clear_output()">&nbsp; <span id="stderr" style="color:red"></span>

        </p>Output:
        <br>
        <textarea id="output" rows="6" style="overflow:scroll;width:90%;"></textarea>
    </div>

<!-- end snippet -->

Alternatively, [here is a JSFiddle](https://jsfiddle.net/mbuettner/rLL2c50u/).

The interpreter is prepopulated [with my quine](http://codegolf.stackexchange.com/a/47112/8478), but you can easily find more example programs for to play around with [by searching for Prelude answers of mine](http://codegolf.stackexchange.com/search?q=prelude+is%3Aanswer+user%3A8478).

All the features of Prelude are implemented. The only shortcoming is the lack of arbitrary-precision arithmetic, due to JavaScript's number type. I might fix that at some point, since I only need to support addition and subtraction for arbitrary integers.

Input and output are usually the byte values of individual characters, but the interpreter comes with two flags to read and/or write (signed) numbers instead.

## ToDo

- Add minified version for copying.
- Support arbitrary precision arithmetic.
- Add snippet generator.