<commit_before>
Drawing = {
    rectangle: function(ctx, centerPt, halfWidth, halfHeight, fillMode) {
        ctx.beginPath();
        ctx.rect(centerPt.x - halfWidth, centerPt.y - halfHeight, 2 * halfWidth, 2 * halfHeight);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    square: function(ctx, centerPt, r, fillMode) {
        ctx.beginPath();
        ctx.rect(centerPt.x - r, centerPt.y - r, 2 * r, 2 * r);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    ellipse: function(ctx, centerPt, halfWidth, halfHeight, fillMode) {
        ctx.beginPath();
        ctx.moveTo(centerPt.x - halfWidth, centerPt.y);
        ctx.bezierCurveTo(centerPt.x - halfWidth, centerPt.y - halfHeight, centerPt.x + halfWidth, centerPt.y - halfHeight, centerPt.x + halfWidth, centerPt.y);
        ctx.bezierCurveTo(centerPt.x + halfWidth, centerPt.y + halfHeight, centerPt.x - halfWidth, centerPt.y + halfHeight, centerPt.x - halfWidth, centerPt.y);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    circle: function(ctx, centerPt, r, fillMode) {
        ctx.beginPath();
        ctx.arc(centerPt.x, centerPt.y, r, 2 * Math.PI, false);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    triangle: function(ctx, centerPt, r, fillMode) {
        ctx.beginPath();
        ctx.moveTo(centerPt.x, centerPt.y - r);
        ctx.lineTo(centerPt.x + r, centerPt.y + r);
        ctx.lineTo(centerPt.x - r, centerPt.y + r);
        ctx.lineTo(centerPt.x, centerPt.y - r);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    fillText: function(ctx, centerPt, text) {
        ctx.fillText(text, centerPt.x, centerPt.y);
    }
};

;(function() {
    var canvas = document.getElementById("drawing")
    var drawing = canvas.getContext('2d');

    function mapLength(obj) {
        var size = 0;
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) size++;
        }
        return size;
    }

    function clone(object) {
        return JSON.parse(JSON.stringify(object));
    }

    Default = {
        background: function() { return "white" },
        lineStyle: function() { return "solid" },
        fillMode: function() { return "empty" }
    };

    /*
    Parse multiple rows of text.
    */
    function parse(input)  {
        var rows = input.split(/\n/);

        var instructions = new Array();
        var metaInstructions = {
            background: { shape: "background", color: Default.background() },
            shapes: { shape: "shapes", lineStyle: Default.lineStyle(), fillMode: Default.fillMode() }
        };

        rows.forEach(function(row) {
            var trimmedRow = row.trim();

            if (trimmedRow.substr(0, 1) == '-') return; // ignore comments

            var rowInstructions = parseRow(trimmedRow);

            if (rowInstructions.metaInstructions) {
                for (var key in rowInstructions.metaInstructions) {
                    metaInstructions[key] = rowInstructions.metaInstructions[key];
                }
            }

            if (rowInstructions.drawingInstructions) {
                instructions.push({ drawingInstructions: rowInstructions.drawingInstructions, metaInstructions: clone(metaInstructions) });
            }
        });

        return instructions;
    }

    function newProperties() {
        return {
            color: null,
            textColor:  null,
            size: null,
            lineStyle: null,
            fillMode: null,
            heightModifier: null,
            cardinality: 1,
            texts: new Array()
        };
    }

    /*
    Parse a single row of text.
    Returns an array of meta instructions and an array of drawing instructions.
    */
    function parseRow(row) {
        var tokens = row.split(/\s+/);

        var metaInstructions = new Object();
        var drawingInstructions = new Array();
        var properties = newProperties();

        tokens.forEach(function(token) {
            switch (token) {
                case "background":
                    metaInstructions[token] = { color: properties.color, shape: token };
                    properties = newProperties();
                    return;

                case "shapes":
                    metaInstructions[token] = {
                        shape: token,
                        lineStyle: properties.lineStyle ? properties.lineStyle : Default.lineStyle(),
                        fillMode: properties.fillMode ? properties.fillMode : Default.fillMode() };
                    properties = newProperties();
                    return;

                case "blank":
                case "blanks":
                    for (var i = 0; i < properties.cardinality; i++) {
                        drawingInstructions.push({ shape: "blank" });
                    }
                    properties = newProperties();
                    return;

                case "square": case "rectangle": case "circle": case "ellipse": case "triangle":
                case "squares": case "rectangles": case "circles": case "ellipses": case "triangles":
                    for (var i = 0; i < properties.cardinality; i++) {
                        drawingInstructions.push({
                            text: properties.texts.length > i ? properties.texts[i] : null,
                            color: properties.color,
                            textColor: properties.textColor,
                            size: properties.size,
                            lineStyle: properties.lineStyle,
                            fillMode: properties.fillMode,
                            heightModifier: properties.heightModifier,
                            widthModifier: properties.widthModifier,
                            shape: token
                        });
                    };
                    properties = newProperties();
                    return;
            }

            switch (token) {
                case "aqua": case "black": case "blue": case "fuchsia": case "gray": case "green": case "lime":  case "maroon": case "navy":
                case "olive": case "orange": case "purple": case "red": case "silver": case "teal": case "white": case "yellow":
                    properties.color = token;
                    return;

                case "tiny": case "small": case "big":
                    properties.size = token;
                    return;

                case "dashed": case "dotted": case "solid":
                    properties.lineStyle = token;
                    return;

                case "filled": case "empty":
                    properties.fillMode = token;
                    return;

                case "full-height": case "half-height": case "quarter-height":
                    properties.heightModifier = token;
                    return;

                case "full-width": case "half-width": case "quarter-width":
                    properties.widthModifier = token;
                    return;
            }

            if (token.search(/^".*"$/) == 0 || token.search(/^'.*'$/) == 0) {
                properties.texts.push(token.substr(1, token.length - 2));
                if (properties.color != null) {
                    properties.textColor = properties.color;
                    properties.color = null;
                }
            }

            if (!isNaN(token)) {
                properties.cardinality = parseInt(token);
                return;
            }
        });

        return {
            metaInstructions : mapLength(metaInstructions) > 0 ? metaInstructions : null,
            drawingInstructions: drawingInstructions.length > 0 ? drawingInstructions : null
        };
    }

    /*
    Render multiple rows of instructions.
    Accepts an array of arrays of instructions.
    */
    function render(instructions) {
        drawing.clearRect(0, 0, canvas.width, canvas.height);

        drawing.textAlign = "center";
        drawing.textBaseline = "middle";

        var rowCount = instructions.length;

        var currentRow = 0;
        instructions.forEach(function(rowInstructions) {
            renderRowBackground(rowInstructions, currentRow, rowCount);
            currentRow++;
        });

        currentRow = 0;
        instructions.forEach(function(rowInstructions) {
            renderRow(rowInstructions, currentRow, rowCount);
            currentRow++;
        });
    }

    function renderRowBackground(rowInstructions, currentRow, rowCount) {
        var columnCount = rowInstructions.drawingInstructions.length;

        for (var key in rowInstructions.metaInstructions) {
            renderBackgroundInstruction(rowInstructions.metaInstructions[key], currentRow, 0, rowCount, columnCount);
        }
    }

    /*
    Render a single row of instructions.
    Accepts an array of instructions.
    */
    function renderRow(rowInstructions, currentRow, rowCount) {
        var columnCount = rowInstructions.drawingInstructions.length;

        for (var key in rowInstructions.metaInstructions) {
            renderInstruction(rowInstructions.metaInstructions[key], currentRow, 0, rowCount, columnCount);
        }

        var currentColumn = 0;
        rowInstructions.drawingInstructions.forEach(function(instruction) {
            renderInstruction(instruction, currentRow, currentColumn, rowCount, columnCount);
            currentColumn++;
        });
    }

    function sizeModifierValue(heightModifier) {
        switch (heightModifier) {
            case "full-height": case "full-width": return 1;
            case "half-height": case "half-width": return 2;
            case "quarter-height": case "quarter-width": return 4;
        }
    }

    /*
    Calculate the center of a shape (e.g. for circles)
    */
    function calcCenter(currentRow, currentColumn, rowCount, columnCount, heightModifier, widthModifier) {
        var cellWidth = canvas.width / columnCount;
        var cellHeight = canvas.height / rowCount;

        var x = function() {
            switch (widthModifier) {
                case "full-width": case "half-width": case "quarter-width":
                    return currentColumn * cellWidth + cellWidth * (columnCount / 2 / sizeModifierValue(widthModifier));
                default:
                    return currentColumn * cellWidth + cellWidth / 2;
            }
        }();

        var y = function() {
            switch (heightModifier) {
                case "full-height": case "half-height": case "quarter-height":
                    return currentRow * cellHeight + cellHeight * (rowCount / 2 / sizeModifierValue(heightModifier));
                default:
                    return currentRow * cellHeight + cellHeight / 2;
            }
        }();

        return {
            x: x,
            y: y
        };
    }

    /*
    Calculate a shapes radius including specified size
    */
    function calcRadius(rowCount, columnCount, size) {
        var factor = 0.75;

        switch (size) {
            case "big":
                factor = 0.95;
                break;

            case "small":
                factor = 0.5;
                break;

            case "tiny":
                factor = 0.25;
                break;
        }

        return Math.min(canvas.width / columnCount / 2, canvas.height / rowCount / 2) * factor;
    }

    function calcHalfWidth(columnCount, size, widthModifier) {
        var factor = 0.8;

        switch (size) {
            case "big":
                factor = 0.95;
                break;

            case "small":
                factor = 0.55;
                break;

            case "tiny":
                factor = 0.3;
                break;
        }

        switch (widthModifier) {
            case "full-width": case "half-width": case "quarter-width":
                return canvas.width / sizeModifierValue(widthModifier) / 2 * factor;

            default:
                return canvas.width / columnCount / 2 * factor;
        }
    }

    function calcHalfHeight(rowCount, size, heightModifier) {
        var factor = 0.7;

        switch (size) {
            case "big":
                factor = 0.95;
                break;

            case "small":
                factor = 0.45;
                break;

            case "tiny":
                factor = 0.2;
                break;
        }

        switch (heightModifier) {
            case "full-height": case "half-height": case "quarter-height":
                return canvas.height / sizeModifierValue(heightModifier) / 2 * factor;

            default:
                return canvas.height / rowCount / 2 * factor;
        }
    }

    function calcFontSize(rowCount, columnCount, size, heightModifier) {
        var factor = 0.75;

        switch (size) {
            case "big":
                factor = 1.0;
                break;

            case "small":
                factor = 0.5;
                break;

            case "tiny":
                factor = 0.25;
                break;
        }

        var yDiv = heightModifier ? 2 : rowCount;

        return Math.min(canvas.width / columnCount / 8, canvas.height / yDiv / 8) * factor;
    }

    function setLineStyle(lineStyle) {
        switch (lineStyle) {
            case "dashed":
                drawing.setLineDash([6]);
                break;
            case "dotted":
                drawing.setLineDash([2, 2]);
                break;
            case "solid":
                drawing.setLineDash([]);
                break;
        }
    }

    function renderBackgroundInstruction(instruction, currentRow, currentColumn, rowCount, columnCount) {
        switch (instruction.shape) {
            case "background":
                drawing.fillStyle = instruction.color;
                drawing.fillRect(0, canvas.height * currentRow / rowCount, canvas.width, canvas.height / rowCount);
                break;
        }
    }

    /*
    Render a single instruction.
    */
    function renderInstruction(instruction, currentRow, currentColumn, rowCount, columnCount) {
        if (instruction.shape == "shapes") {
            canvas.lineStyle = instruction.lineStyle;
            canvas.fillMode = instruction.fillMode;
            return;
        }

        drawing.fillStyle = drawing.strokeStyle = (instruction.color != null ? instruction.color : "black");
        setLineStyle(instruction.lineStyle ? instruction.lineStyle : canvas.lineStyle);

        switch (instruction.shape) {
            case "square":
            case "squares":
                Drawing.square(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcRadius(rowCount, columnCount, instruction.size), instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;

            case "rectangle":
            case "rectangles":
                Drawing.rectangle(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcHalfWidth(columnCount, instruction.size, instruction.widthModifier), calcHalfHeight(rowCount, instruction.size, instruction.heightModifier),
                    instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;

            case "circle":
            case "circles":
                Drawing.circle(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcRadius(rowCount, columnCount, instruction.size), instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;

            case "ellipse":
            case "ellipses":
                Drawing.ellipse(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcHalfWidth(columnCount, instruction.size, instruction.widthModifier), calcHalfHeight(rowCount, instruction.size, instruction.heightModifier),
                    instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;

            case "triangle":
            case "triangles":
                Drawing.triangle(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcRadius(rowCount, columnCount, instruction.size), instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;
        }

        if (instruction.text) {
            drawing.fillStyle = instruction.textColor ? instruction.textColor : "black";
            drawing.font = calcFontSize(rowCount, columnCount, instruction.size, instruction.heightModifier) + "pt Arial";
            Drawing.fillText(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier), instruction.text);
        }
    }

    window.onload = function() {
        var input = document.getElementById("input");

        var draw = function() {
            instructions = parse(input.value);
            render(instructions);

            window.requestAnimationFrame(draw);
        };

        window.requestAnimationFrame(draw);
    };
})();<commit_msg>Use strict<commit_after>"use strict";

var Drawing = {
    rectangle: function(ctx, centerPt, halfWidth, halfHeight, fillMode) {
        ctx.beginPath();
        ctx.rect(centerPt.x - halfWidth, centerPt.y - halfHeight, 2 * halfWidth, 2 * halfHeight);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    square: function(ctx, centerPt, r, fillMode) {
        ctx.beginPath();
        ctx.rect(centerPt.x - r, centerPt.y - r, 2 * r, 2 * r);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    ellipse: function(ctx, centerPt, halfWidth, halfHeight, fillMode) {
        ctx.beginPath();
        ctx.moveTo(centerPt.x - halfWidth, centerPt.y);
        ctx.bezierCurveTo(centerPt.x - halfWidth, centerPt.y - halfHeight, centerPt.x + halfWidth, centerPt.y - halfHeight, centerPt.x + halfWidth, centerPt.y);
        ctx.bezierCurveTo(centerPt.x + halfWidth, centerPt.y + halfHeight, centerPt.x - halfWidth, centerPt.y + halfHeight, centerPt.x - halfWidth, centerPt.y);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    circle: function(ctx, centerPt, r, fillMode) {
        ctx.beginPath();
        ctx.arc(centerPt.x, centerPt.y, r, 2 * Math.PI, false);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    triangle: function(ctx, centerPt, r, fillMode) {
        ctx.beginPath();
        ctx.moveTo(centerPt.x, centerPt.y - r);
        ctx.lineTo(centerPt.x + r, centerPt.y + r);
        ctx.lineTo(centerPt.x - r, centerPt.y + r);
        ctx.lineTo(centerPt.x, centerPt.y - r);
        if (fillMode == "filled") ctx.fill();
        ctx.stroke();
    },

    fillText: function(ctx, centerPt, text) {
        ctx.fillText(text, centerPt.x, centerPt.y);
    }
};

;(function() {
    var canvas = document.getElementById("drawing")
    var drawing = canvas.getContext('2d');

    function mapLength(obj) {
        var size = 0;
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) size++;
        }
        return size;
    }

    function clone(object) {
        return JSON.parse(JSON.stringify(object));
    }

    var Default = {
        background: function() { return "white" },
        lineStyle: function() { return "solid" },
        fillMode: function() { return "empty" }
    };

    /*
    Parse multiple rows of text.
    */
    function parse(input)  {
        var rows = input.split(/\n/);

        var instructions = new Array();
        var metaInstructions = {
            background: { shape: "background", color: Default.background() },
            shapes: { shape: "shapes", lineStyle: Default.lineStyle(), fillMode: Default.fillMode() }
        };

        rows.forEach(function(row) {
            var trimmedRow = row.trim();

            if (trimmedRow.substr(0, 1) == '-') return; // ignore comments

            var rowInstructions = parseRow(trimmedRow);

            if (rowInstructions.metaInstructions) {
                for (var key in rowInstructions.metaInstructions) {
                    metaInstructions[key] = rowInstructions.metaInstructions[key];
                }
            }

            if (rowInstructions.drawingInstructions) {
                instructions.push({ drawingInstructions: rowInstructions.drawingInstructions, metaInstructions: clone(metaInstructions) });
            }
        });

        return instructions;
    }

    function newProperties() {
        return {
            color: null,
            textColor:  null,
            size: null,
            lineStyle: null,
            fillMode: null,
            heightModifier: null,
            cardinality: 1,
            texts: new Array()
        };
    }

    /*
    Parse a single row of text.
    Returns an array of meta instructions and an array of drawing instructions.
    */
    function parseRow(row) {
        var tokens = row.split(/\s+/);

        var metaInstructions = new Object();
        var drawingInstructions = new Array();
        var properties = newProperties();

        tokens.forEach(function(token) {
            switch (token) {
                case "background":
                    metaInstructions[token] = { color: properties.color, shape: token };
                    properties = newProperties();
                    return;

                case "shapes":
                    metaInstructions[token] = {
                        shape: token,
                        lineStyle: properties.lineStyle ? properties.lineStyle : Default.lineStyle(),
                        fillMode: properties.fillMode ? properties.fillMode : Default.fillMode() };
                    properties = newProperties();
                    return;

                case "blank":
                case "blanks":
                    for (var i = 0; i < properties.cardinality; i++) {
                        drawingInstructions.push({ shape: "blank" });
                    }
                    properties = newProperties();
                    return;

                case "square": case "rectangle": case "circle": case "ellipse": case "triangle":
                case "squares": case "rectangles": case "circles": case "ellipses": case "triangles":
                    for (var i = 0; i < properties.cardinality; i++) {
                        drawingInstructions.push({
                            text: properties.texts.length > i ? properties.texts[i] : null,
                            color: properties.color,
                            textColor: properties.textColor,
                            size: properties.size,
                            lineStyle: properties.lineStyle,
                            fillMode: properties.fillMode,
                            heightModifier: properties.heightModifier,
                            widthModifier: properties.widthModifier,
                            shape: token
                        });
                    };
                    properties = newProperties();
                    return;
            }

            switch (token) {
                case "aqua": case "black": case "blue": case "fuchsia": case "gray": case "green": case "lime":  case "maroon": case "navy":
                case "olive": case "orange": case "purple": case "red": case "silver": case "teal": case "white": case "yellow":
                    properties.color = token;
                    return;

                case "tiny": case "small": case "big":
                    properties.size = token;
                    return;

                case "dashed": case "dotted": case "solid":
                    properties.lineStyle = token;
                    return;

                case "filled": case "empty":
                    properties.fillMode = token;
                    return;

                case "full-height": case "half-height": case "quarter-height":
                    properties.heightModifier = token;
                    return;

                case "full-width": case "half-width": case "quarter-width":
                    properties.widthModifier = token;
                    return;
            }

            if (token.search(/^".*"$/) == 0 || token.search(/^'.*'$/) == 0) {
                properties.texts.push(token.substr(1, token.length - 2));
                if (properties.color != null) {
                    properties.textColor = properties.color;
                    properties.color = null;
                }
            }

            if (!isNaN(token)) {
                properties.cardinality = parseInt(token);
                return;
            }
        });

        return {
            metaInstructions : mapLength(metaInstructions) > 0 ? metaInstructions : null,
            drawingInstructions: drawingInstructions.length > 0 ? drawingInstructions : null
        };
    }

    /*
    Render multiple rows of instructions.
    Accepts an array of arrays of instructions.
    */
    function render(instructions) {
        drawing.clearRect(0, 0, canvas.width, canvas.height);

        drawing.textAlign = "center";
        drawing.textBaseline = "middle";

        var rowCount = instructions.length;

        var currentRow = 0;
        instructions.forEach(function(rowInstructions) {
            renderRowBackground(rowInstructions, currentRow, rowCount);
            currentRow++;
        });

        currentRow = 0;
        instructions.forEach(function(rowInstructions) {
            renderRow(rowInstructions, currentRow, rowCount);
            currentRow++;
        });
    }

    function renderRowBackground(rowInstructions, currentRow, rowCount) {
        var columnCount = rowInstructions.drawingInstructions.length;

        for (var key in rowInstructions.metaInstructions) {
            renderBackgroundInstruction(rowInstructions.metaInstructions[key], currentRow, 0, rowCount, columnCount);
        }
    }

    /*
    Render a single row of instructions.
    Accepts an array of instructions.
    */
    function renderRow(rowInstructions, currentRow, rowCount) {
        var columnCount = rowInstructions.drawingInstructions.length;

        for (var key in rowInstructions.metaInstructions) {
            renderInstruction(rowInstructions.metaInstructions[key], currentRow, 0, rowCount, columnCount);
        }

        var currentColumn = 0;
        rowInstructions.drawingInstructions.forEach(function(instruction) {
            renderInstruction(instruction, currentRow, currentColumn, rowCount, columnCount);
            currentColumn++;
        });
    }

    function sizeModifierValue(heightModifier) {
        switch (heightModifier) {
            case "full-height": case "full-width": return 1;
            case "half-height": case "half-width": return 2;
            case "quarter-height": case "quarter-width": return 4;
        }
    }

    /*
    Calculate the center of a shape (e.g. for circles)
    */
    function calcCenter(currentRow, currentColumn, rowCount, columnCount, heightModifier, widthModifier) {
        var cellWidth = canvas.width / columnCount;
        var cellHeight = canvas.height / rowCount;

        var x = function() {
            switch (widthModifier) {
                case "full-width": case "half-width": case "quarter-width":
                    return currentColumn * cellWidth + cellWidth * (columnCount / 2 / sizeModifierValue(widthModifier));
                default:
                    return currentColumn * cellWidth + cellWidth / 2;
            }
        }();

        var y = function() {
            switch (heightModifier) {
                case "full-height": case "half-height": case "quarter-height":
                    return currentRow * cellHeight + cellHeight * (rowCount / 2 / sizeModifierValue(heightModifier));
                default:
                    return currentRow * cellHeight + cellHeight / 2;
            }
        }();

        return {
            x: x,
            y: y
        };
    }

    /*
    Calculate a shapes radius including specified size
    */
    function calcRadius(rowCount, columnCount, size) {
        var factor = 0.75;

        switch (size) {
            case "big":
                factor = 0.95;
                break;

            case "small":
                factor = 0.5;
                break;

            case "tiny":
                factor = 0.25;
                break;
        }

        return Math.min(canvas.width / columnCount / 2, canvas.height / rowCount / 2) * factor;
    }

    function calcHalfWidth(columnCount, size, widthModifier) {
        var factor = 0.8;

        switch (size) {
            case "big":
                factor = 0.95;
                break;

            case "small":
                factor = 0.55;
                break;

            case "tiny":
                factor = 0.3;
                break;
        }

        switch (widthModifier) {
            case "full-width": case "half-width": case "quarter-width":
                return canvas.width / sizeModifierValue(widthModifier) / 2 * factor;

            default:
                return canvas.width / columnCount / 2 * factor;
        }
    }

    function calcHalfHeight(rowCount, size, heightModifier) {
        var factor = 0.7;

        switch (size) {
            case "big":
                factor = 0.95;
                break;

            case "small":
                factor = 0.45;
                break;

            case "tiny":
                factor = 0.2;
                break;
        }

        switch (heightModifier) {
            case "full-height": case "half-height": case "quarter-height":
                return canvas.height / sizeModifierValue(heightModifier) / 2 * factor;

            default:
                return canvas.height / rowCount / 2 * factor;
        }
    }

    function calcFontSize(rowCount, columnCount, size, heightModifier) {
        var factor = 0.75;

        switch (size) {
            case "big":
                factor = 1.0;
                break;

            case "small":
                factor = 0.5;
                break;

            case "tiny":
                factor = 0.25;
                break;
        }

        var yDiv = heightModifier ? 2 : rowCount;

        return Math.min(canvas.width / columnCount / 8, canvas.height / yDiv / 8) * factor;
    }

    function setLineStyle(lineStyle) {
        switch (lineStyle) {
            case "dashed":
                drawing.setLineDash([6]);
                break;
            case "dotted":
                drawing.setLineDash([2, 2]);
                break;
            case "solid":
                drawing.setLineDash([]);
                break;
        }
    }

    function renderBackgroundInstruction(instruction, currentRow, currentColumn, rowCount, columnCount) {
        switch (instruction.shape) {
            case "background":
                drawing.fillStyle = instruction.color;
                drawing.fillRect(0, canvas.height * currentRow / rowCount, canvas.width, canvas.height / rowCount);
                break;
        }
    }

    /*
    Render a single instruction.
    */
    function renderInstruction(instruction, currentRow, currentColumn, rowCount, columnCount) {
        if (instruction.shape == "shapes") {
            canvas.lineStyle = instruction.lineStyle;
            canvas.fillMode = instruction.fillMode;
            return;
        }

        drawing.fillStyle = drawing.strokeStyle = (instruction.color != null ? instruction.color : "black");
        setLineStyle(instruction.lineStyle ? instruction.lineStyle : canvas.lineStyle);

        switch (instruction.shape) {
            case "square":
            case "squares":
                Drawing.square(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcRadius(rowCount, columnCount, instruction.size), instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;

            case "rectangle":
            case "rectangles":
                Drawing.rectangle(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcHalfWidth(columnCount, instruction.size, instruction.widthModifier), calcHalfHeight(rowCount, instruction.size, instruction.heightModifier),
                    instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;

            case "circle":
            case "circles":
                Drawing.circle(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcRadius(rowCount, columnCount, instruction.size), instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;

            case "ellipse":
            case "ellipses":
                Drawing.ellipse(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcHalfWidth(columnCount, instruction.size, instruction.widthModifier), calcHalfHeight(rowCount, instruction.size, instruction.heightModifier),
                    instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;

            case "triangle":
            case "triangles":
                Drawing.triangle(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier),
                    calcRadius(rowCount, columnCount, instruction.size), instruction.fillMode ? instruction.fillMode : canvas.fillMode);
                break;
        }

        if (instruction.text) {
            drawing.fillStyle = instruction.textColor ? instruction.textColor : "black";
            drawing.font = calcFontSize(rowCount, columnCount, instruction.size, instruction.heightModifier) + "pt Arial";
            Drawing.fillText(drawing, calcCenter(currentRow, currentColumn, rowCount, columnCount, instruction.heightModifier, instruction.widthModifier), instruction.text);
        }
    }

    window.onload = function() {
        var input = document.getElementById("input");

        var draw = function() {
            var instructions = parse(input.value);
            render(instructions);

            window.requestAnimationFrame(draw);
        };

        window.requestAnimationFrame(draw);
    };
})();<|endoftext|>


<commit_before>var xml2js = require('xml2js'),
	underscore = require('underscore');

// Tidies up and optionally parses the XML API response
//
// The `options` argument should contain the following properties:
//
// - *format* - The desired response format
// - *logger* - An instance of {Winston.Logger} for output
//
// - @param {Object} options
// - @constructor
function ResponseParser(options) {
	this.format = options.format;
	this.logger = options.logger;
}

// The API returns resources as either a single object or an array.
// This makes responses fiddly to deal with for consumers as they
// manually check whether the resource has a length an access the
// property appropriately.  This method checks for an object and
// wraps it in an array if it is.
//
// - @param {Object} response
// - @return {Object} the normalised response
ResponseParser.prototype.normaliseResourceArrays = function (response) {
	var items;

	underscore([
			'tracks.track',
			'artists.artist',
			'chart.chartItem',
			'releases.release',
			'searchResult.artist',
			'recommendations.recommendedItem',
			'searchResult.release',
			'tags.tag',
			'taggedResults.taggedItem'
		]).each(function checkLength(item) {
			var parts = item.split('.'),
				parent = parts[0],
				child = parts[1];

			if (response[parent] && response[parent][child] &&
				!response[parent][child].length) {
				response[parent][child] = [response[parent][child]];
			}
		});

	if (response.basket && response.basket.basketItems
			&& response.basket.basketItems.basketItem) {
		items = response.basket.basketItems.basketItem;
		items = items.length ? items : [items];
		response.basket.basketItems = items;
	}

	return response;
};

// Callback for parsing the XML response return from the API
// and converting it to JSON and handing control back to the
// caller.
//
// - @param {Function} callback - the caller's callback
// - @param {String} response - the XML response from the API
ResponseParser.prototype.parse = function (callback, err, response) {
	var parser,
		self = this;

	if (err) {
		callback(err);
		return;
	}

	if (this.format.toUpperCase() === 'XML') {
		callback(null, response);
		return;
	}

	parser = new xml2js.Parser({ mergeAttrs: true, explicitArray: false });

	// Check the request was authorised
	if (response.statusCode === 401) {
		callback(response);
		return;
	}

	this.logger.silly(response);
	this.logger.info('Parsing XML response from API');
	parser.on('end', function (result) {
		// Manually remove the xml namespace bits
		delete result['xmlns:xsi'];
		delete result['xsi:noNamespaceSchemaLocation'];
		if (result.response.status === 'error') {
			callback(result.response.error);
		}
		else {
			var normalisedResult = self.normaliseResourceArrays(result.response);
			callback(null, normalisedResult);
		}
	});
	parser.parseString(response + '');
};

module.exports = ResponseParser;
<commit_msg>Remove superfluous logging<commit_after>var xml2js = require('xml2js'),
	underscore = require('underscore');

// Tidies up and optionally parses the XML API response
//
// The `options` argument should contain the following properties:
//
// - *format* - The desired response format
// - *logger* - An instance of {Winston.Logger} for output
//
// - @param {Object} options
// - @constructor
function ResponseParser(options) {
	this.format = options.format;
	this.logger = options.logger;
}

// The API returns resources as either a single object or an array.
// This makes responses fiddly to deal with for consumers as they
// manually check whether the resource has a length an access the
// property appropriately.  This method checks for an object and
// wraps it in an array if it is.
//
// - @param {Object} response
// - @return {Object} the normalised response
ResponseParser.prototype.normaliseResourceArrays = function (response) {
	var items;

	underscore([
			'tracks.track',
			'artists.artist',
			'chart.chartItem',
			'releases.release',
			'searchResult.artist',
			'recommendations.recommendedItem',
			'searchResult.release',
			'tags.tag',
			'taggedResults.taggedItem'
		]).each(function checkLength(item) {
			var parts = item.split('.'),
				parent = parts[0],
				child = parts[1];

			if (response[parent] && response[parent][child] &&
				!response[parent][child].length) {
				response[parent][child] = [response[parent][child]];
			}
		});

	if (response.basket && response.basket.basketItems
			&& response.basket.basketItems.basketItem) {
		items = response.basket.basketItems.basketItem;
		items = items.length ? items : [items];
		response.basket.basketItems = items;
	}

	return response;
};

// Callback for parsing the XML response return from the API
// and converting it to JSON and handing control back to the
// caller.
//
// - @param {Function} callback - the caller's callback
// - @param {String} response - the XML response from the API
ResponseParser.prototype.parse = function (callback, err, response) {
	var parser,
		self = this;

	if (err) {
		callback(err);
		return;
	}

	if (this.format.toUpperCase() === 'XML') {
		callback(null, response);
		return;
	}

	parser = new xml2js.Parser({ mergeAttrs: true, explicitArray: false });

	// Check the request was authorised
	if (response.statusCode === 401) {
		callback(response);
		return;
	}

	this.logger.silly(response);
	parser.on('end', function (result) {
		// Manually remove the xml namespace bits
		delete result['xmlns:xsi'];
		delete result['xsi:noNamespaceSchemaLocation'];
		if (result.response.status === 'error') {
			callback(result.response.error);
		}
		else {
			var normalisedResult = self.normaliseResourceArrays(result.response);
			callback(null, normalisedResult);
		}
	});
	parser.parseString(response + '');
};

module.exports = ResponseParser;
<|endoftext|>


<commit_before>const fs = require('fs-extra');
const browserify = require('browserify');
const liveServer = require('live-server');
const watch = require('watch');
const uglifyJS = require('uglify-js');
const ghpages = require('gh-pages');
const esdoc = require('esdoc');

const DIST_DIR = 'dist/';
const COMPILED_FILENAME = 'monogatari.js';
const MINIFIED_FILENAME = 'monogatari.min.js';
const VERSION = process.env.npm_package_version;
const DIST_DIR_VERSION = DIST_DIR + VERSION + '/';
const HEADER_TEXT = '// monogatari v' + VERSION + '\n';

const LIVE_SERVER_CONFIG = {
    port: 8080,
    host: '0.0.0.0',
    root: './',
    watch: ['monogatari*.js', 'usage'],
    open: false,
    wait: 1000,
    logLevel: 2
};

const BROWSERIFY_CONFIG = {
    entries: 'src/main/Monogatari.js',
    paths: ['src/main/'],
    standalone: 'Monogatari', // wrap with UMD
    noParse: [
        require.resolve('three'),
        require.resolve('howler'),
        require.resolve('./lib/Box2D_v2.3.1_min')
    ]
};

const ESDOC_CONFIG = {
    "source": "src/main",
    "destination": DIST_DIR_VERSION + "docs",
    "plugins": [{
        "name": "esdoc-standard-plugin"
    }]
}

const clean = () => {
    fs.removeSync(DIST_DIR);
    fs.removeSync(COMPILED_FILENAME);
}

const compile = (callback) => {
    browserify(BROWSERIFY_CONFIG)
        .transform('babelify', {
            presets: ['env'],
            ignore: 'Box2D_v2.3.1_min*'
        })
        .bundle(function (err, bundleBuf) {
            if (err) throw err;
            else {
                fs.writeFileSync(COMPILED_FILENAME, HEADER_TEXT + bundleBuf.toString('utf-8'));
                if (callback) callback();
            }
        });
};

const package = (callback) => {
    compile(() => {
        esdoc.default.generate(ESDOC_CONFIG);
        var compiled = fs.readFileSync(COMPILED_FILENAME, 'utf-8');
        var minified = uglifyJS.minify(compiled).code;
        fs.writeFileSync(DIST_DIR_VERSION + MINIFIED_FILENAME, HEADER_TEXT + minified);
        if (callback) callback();
    });
}

const deploy = () => {
    package(() => {
        var repo = process.env.GH_TOKEN ? 'https://' + process.env.GH_TOKEN + '@github.com/digihaus/monogatari.git' : undefined;
        ghpages.publish(DIST_DIR_VERSION, { dest: VERSION, repo: repo }, (err) => { if (err) throw err });
    });
};

const live = () => {
    watch.watchTree('./src', { interval: 1 }, (f, curr, prev) => {
        compile();
    });
    liveServer.start(LIVE_SERVER_CONFIG);
}

const args = process.argv.slice(2);

if (args[0] === "clean") clean();
else if (args[0] === "compile") compile();
else if (args[0] === "package") package();
else if (args[0] === "deploy") deploy();
else if (args[0] === "live") live();<commit_msg>Fixing build<commit_after>const fs = require('fs-extra');
const browserify = require('browserify');
const liveServer = require('live-server');
const watch = require('watch');
const uglifyJS = require('uglify-js');
const ghpages = require('gh-pages');
const esdoc = require('esdoc');

const DIST_DIR = 'dist/';
const COMPILED_FILENAME = 'monogatari.js';
const MINIFIED_FILENAME = 'monogatari.min.js';
const VERSION = process.env.npm_package_version;
const DIST_DIR_VERSION = DIST_DIR + VERSION + '/';
const HEADER_TEXT = '// monogatari v' + VERSION + '\n';

const LIVE_SERVER_CONFIG = {
    port: 8080,
    host: '0.0.0.0',
    root: './',
    watch: ['monogatari*.js', 'usage'],
    open: false,
    wait: 1000,
    logLevel: 2
};

const BROWSERIFY_CONFIG = {
    entries: 'src/main/Monogatari.js',
    paths: ['src/main/'],
    fullPaths: false,
    standalone: 'Monogatari', // wrap with UMD
    noParse: [
        require.resolve('three'),
        require.resolve('howler'),
        require.resolve('./lib/Box2D_v2.3.1_min')
    ]
};

const ESDOC_CONFIG = {
    "source": "src/main",
    "destination": DIST_DIR_VERSION + "docs",
    "plugins": [{
        "name": "esdoc-standard-plugin"
    }]
}

const clean = () => {
    fs.removeSync(DIST_DIR);
    fs.removeSync(COMPILED_FILENAME);
}

const compile = (callback) => {
    browserify(BROWSERIFY_CONFIG)
        .transform('babelify', {
            presets: ['env'],
            ignore: 'Box2D_v2.3.1_min*'
        })
        .bundle((err, bundleBuf) => {
            if (err) throw err;
            else {
                fs.writeFileSync(COMPILED_FILENAME, HEADER_TEXT + bundleBuf.toString('utf-8'));
                if (callback) callback();
            }
        });
};

const package = (callback) => {
    compile(() => {
        esdoc.default.generate(ESDOC_CONFIG);
        var compiled = fs.readFileSync(COMPILED_FILENAME, 'utf-8');
        var minified = uglifyJS.minify(compiled).code;
        fs.writeFileSync(DIST_DIR_VERSION + MINIFIED_FILENAME, HEADER_TEXT + minified);
        if (callback) callback();
    });
}

const deploy = () => {
    package(() => {
        var repo = process.env.GH_TOKEN ? 'https://' + process.env.GH_TOKEN + '@github.com/digihaus/monogatari.git' : undefined;
        ghpages.publish(DIST_DIR_VERSION, { dest: VERSION, repo: repo }, (err) => { if (err) throw err });
    });
};

const live = () => {
    watch.watchTree('./src', { interval: 1 }, (f, curr, prev) => {
        compile();
    });
    liveServer.start(LIVE_SERVER_CONFIG);
}

const args = process.argv.slice(2);

if (args[0] === "clean") clean();
else if (args[0] === "compile") compile();
else if (args[0] === "package") package();
else if (args[0] === "deploy") deploy();
else if (args[0] === "live") live();<|endoftext|>


<commit_before>"use strict";
const { Command, SerializedArgumentParser, ParsingError } = require("sosamba");
const { prototype: { permissionCheck: isOwner } } = require("../lib/OwnerCommand");
const config = require("../config");
const speakerPhone = require("../lib/speakerPhone.v4");
const RegisterSymbol = Symbol("tt.bot.phone.register");
const CallSymbol = Symbol("tt.bot.phone.call");
const LookupSymbol = Symbol("tt.bot.phone.lookup");
const DeleteSymbol = Symbol("tt.bot.phone.delete");

const convertNumber = input => input
    .replace(/(a|b|c)/ig, "2")
    .replace(/(d|e|f)/ig, "3")
    .replace(/(g|h|i)/ig, "4")
    .replace(/(j|k|l)/ig, "5")
    .replace(/(m|n|o)/ig, "6")
    .replace(/(p|q|r|s)/ig, "7")
    .replace(/(t|u|v)/ig, "8")
    .replace(/(w|x|y|z)/ig, "9")
    .replace(/(-|\(|\))/g, "")
    .replace(/\s+/g, "");
const checkValidNumber = n => !isNaN(n) &&
    n.length === 14 && // TTBOT and the 9-digit number
    /^88268/.test(n);

class PhoneCommand extends Command {
    constructor(sosamba, ...args) {
        super(sosamba, ...args, {
            name: "phone",
            argParser: new SerializedArgumentParser(sosamba, {
                allowQuotedString: true,
                args: [{
                    name: "action",
                    type: (val, ctx) => {
                        if (val === "register" && sosamba.isAdmin(ctx.member)) return RegisterSymbol;
                        if (val === "call") return CallSymbol;
                        if (val === "lookup") return LookupSymbol;
                        if (val === "delete" && sosamba.isAdmin(ctx.member)) return DeleteSymbol;
                        throw new ParsingError("Invalid action");
                    },
                    description: "the action to do",
                }, {
                    name: "number",
                    type: async (val, ctx) => {
                        const num = convertNumber(val);
                        console.log(num);
                        if (!checkValidNumber(num))
                            throw new ParsingError(await ctx.t("NUMBER_INVALID", true));
                        return num;
                    },
                    rest: true
                }]
            }),
            description: "Talk with people across Discord."
        });

        this.speakerPhone = sosamba.messageListeners.add(new speakerPhone(sosamba));
    }

    async run(ctx, [action, number]) {
        if (action === RegisterSymbol) {
            const phones = await ctx.db.getGuildPhoneNumbers(ctx.guild.id);
            if (phones.length > 0 && !isOwner({
                author: {
                    id: ctx.guild.ownerID
                }
            })) {
                await ctx.send(await ctx.t("ALREADY_HAVE_NUMBER"));
                return;
            }

            if (number.startsWith("882680") && !isOwner({
                author: {
                    id: ctx.guild.ownerID
                }
            })) {
                await ctx.send(await ctx.t("NUMBER_RESERVED"));
                return;
            }

            if (await ctx.db.getPhoneNumber(number)) {
                await ctx.send(await ctx.t("NUMBER_EXISTS"));
                return;
            }

            await ctx.send(await ctx.t("QUESTION_NUMBER_PRIVATE"));
            const isPrivate = await ctx.askYesNo();
            await ctx.db.createPhoneNumber({
                id: number,
                private: isPrivate,
                channelID: ctx.channel.id,
                guildID: ctx.guild.id
            });
            await ctx.send(await ctx.t("NUMBER_CREATED"));
        } else if (action === CallSymbol) {
            const [thisChannelNumber] = await ctx.db.getChannelPhoneNumbers(ctx.guild.id, ctx.channel.id);
            if (!thisChannelNumber) {
                await ctx.send(await ctx.t("CALLER_NO_NUMBER"));
                return;
            }
            const otherSideNumber = await ctx.db.getPhoneNumber(number);
            if (!otherSideNumber) {
                await ctx.send(await ctx.t("NUMBER_NONEXISTANT"));
                return;
            }

            await ctx.send(await ctx.t("CALLING"));

            try {
                await this.sosamba.createMessage(otherSideNumber.channelID,
                    `Incoming call by ${thisChannelNumber.id} ${!thisChannelNumber.private ? `(#${ctx.channel.name} at ${ctx.guild.name})` : ""}\nType \`${config.prefix}pickup\` to pick up the call. Else, type \`${config.prefix}hangup\`. You have 2 minutes to pick up the call, else the call will be automatically hung up.`);
                try {
                    const answer = await ctx.waitForAnyMessage(otherSideNumber.channelID,
                        ctx => (ctx.msg.content === `${config.prefix}pickup`
                            || ctx.msg.content === `${config.prefix}hangup`),
                    2 * 60e3);

                    if (answer.msg.content === `${config.prefix}pickup`) {
                        this.speakerPhone.addChannels(ctx.channel, this.sosamba.getChannel(otherSideNumber.channelID), thisChannelNumber, otherSideNumber);
                    } else {
                        for (const cID of [otherSideNumber.channelID, ctx.channel.id]) {
                            await this.sosamba.createMessage(cID, "The call was hung up.");
                        }
                    }
                } catch (err) {
                    console.error(err);
                    for (const cID of [otherSideNumber.channelID, ctx.channel.id]) {
                        await this.sosamba.createMessage(cID, "The call timed out.");
                    }
                }
            } catch {
                if (!this.sosamba.guilds.has(otherSideNumber.guildID)) {
                    await ctx.send(await ctx.t("CALL_ABORTED_BOT_REMOVED"));
                    const phoneNumbers = await ctx.db.getGuildPhoneNumbers(otherSideNumber.guildID);
                    await Promise.all(phoneNumbers.map(async ({ id }) => ctx.db.deletePhoneNumber(id)));
                    return;
                } else {
                    await ctx.send(await ctx.t("CALL_ABORTED_NO_PERMISSIONS"));
                    return;
                }

            }
        } else if (action === LookupSymbol) {
            const data = await ctx.db.getPhoneNumber(number);

            if (!data) {
                await ctx.send(await ctx.t("NUMBER_NONEXISTANT"));
                return;
            }

            const fields = [{
                name: await ctx.t("PRIVATE_NUMBER"),
                value: data.private ? await ctx.t("YES")
                    : await ctx.t("NO"),
                inline: true
            }];

            const isOnServer = this.sosamba.guilds.has(data.guildID);
            if (!isOnServer) {
                const phoneNumbersInDelGuild = await ctx.db.getGuildPhoneNumbers(data.guildID);
                await Promise.all(phoneNumbersInDelGuild.map(async ({ id }) => ctx.db.deletePhoneNumber(id)));
                console.log(`Deleted the phone numbers from ${data.guildID}: ${phoneNumbersInDelGuild.map(d => d.id).join(", ")}`);
            }

            const c = this.sosamba.getChannel(data.channelID);
            if (!data.private || !isOnServer) fields.push({
                name: await ctx.t("CHANNEL_INFORMATION"),
                value: isOnServer ? await ctx.t("CHANNEL_INFORMATION_VALUE", c, {
                    name: this.sosamba.escapeMarkdown(c.guild.name)
                }) : await ctx.t("NUMBER_AVAILABLE"),
                inline: true
            });

            await ctx.send({
                embed: {
                    author: {
                        name: await ctx.t("NUMBER_INFORMATION", data.id)
                    },
                    fields,
                    color: 0x008800
                }
            });
        } else if (action === DeleteSymbol) {
            const phone = ctx.db.getPhoneNumber(number);
            if (!phone || (!isOwner(ctx)
                && (phone && (phone.guildID !== ctx.guild.id)))
            ) {
                await ctx.send(await ctx.t("NUMBER_NONEXISTANT"));
                return;
            }
            await ctx.send(await ctx.t("QUESTION_DELETE_NUM", number));

            if (await ctx.askYesNo()) {
                await ctx.db.deletePhoneNumber(number);
                await ctx.send(await ctx.t("NUMBER_DELETED"));
            } else {
                await ctx.send(await ctx.t("OP_CANCELLED"));
            }
        }
    }
}

module.exports = PhoneCommand;<commit_msg>Fix linting issues for 6f2985935a1687ce7e7463f4d7781f98e3c7f743<commit_after>"use strict";
const { Command, SerializedArgumentParser, ParsingError } = require("sosamba");
const { prototype: { permissionCheck: isOwner } } = require("../lib/OwnerCommand");
const config = require("../config");
const speakerPhone = require("../lib/speakerPhone.v4");
const RegisterSymbol = Symbol("tt.bot.phone.register");
const CallSymbol = Symbol("tt.bot.phone.call");
const LookupSymbol = Symbol("tt.bot.phone.lookup");
const DeleteSymbol = Symbol("tt.bot.phone.delete");

const convertNumber = input => input
    .replace(/(a|b|c)/ig, "2")
    .replace(/(d|e|f)/ig, "3")
    .replace(/(g|h|i)/ig, "4")
    .replace(/(j|k|l)/ig, "5")
    .replace(/(m|n|o)/ig, "6")
    .replace(/(p|q|r|s)/ig, "7")
    .replace(/(t|u|v)/ig, "8")
    .replace(/(w|x|y|z)/ig, "9")
    .replace(/(-|\(|\))/g, "")
    .replace(/\s+/g, "");
const checkValidNumber = n => !isNaN(n) &&
    n.length === 14 && // TTBOT and the 9-digit number
    /^88268/.test(n);

class PhoneCommand extends Command {
    constructor(sosamba, ...args) {
        super(sosamba, ...args, {
            name: "phone",
            argParser: new SerializedArgumentParser(sosamba, {
                allowQuotedString: true,
                args: [{
                    name: "action",
                    type: (val, ctx) => {
                        if (val === "register" && sosamba.isAdmin(ctx.member)) return RegisterSymbol;
                        if (val === "call") return CallSymbol;
                        if (val === "lookup") return LookupSymbol;
                        if (val === "delete" && sosamba.isAdmin(ctx.member)) return DeleteSymbol;
                        throw new ParsingError("Invalid action");
                    },
                    description: "the action to do",
                }, {
                    name: "number",
                    type: async (val, ctx) => {
                        const num = convertNumber(val);
                        console.log(num);
                        if (!checkValidNumber(num))
                            throw new ParsingError(await ctx.t("NUMBER_INVALID", true));
                        return num;
                    },
                    rest: true
                }]
            }),
            description: "Talk with people across Discord."
        });

        this.speakerPhone = sosamba.messageListeners.add(new speakerPhone(sosamba));
    }

    async run(ctx, [action, number]) {
        if (action === RegisterSymbol) {
            const phones = await ctx.db.getGuildPhoneNumbers(ctx.guild.id);
            if (phones.length > 0 && !isOwner({
                author: {
                    id: ctx.guild.ownerID
                }
            })) {
                await ctx.send(await ctx.t("ALREADY_HAVE_NUMBER"));
                return;
            }

            if (number.startsWith("882680") && !isOwner({
                author: {
                    id: ctx.guild.ownerID
                }
            })) {
                await ctx.send(await ctx.t("NUMBER_RESERVED"));
                return;
            }

            if (await ctx.db.getPhoneNumber(number)) {
                await ctx.send(await ctx.t("NUMBER_EXISTS"));
                return;
            }

            await ctx.send(await ctx.t("QUESTION_NUMBER_PRIVATE"));
            const isPrivate = await ctx.askYesNo();
            await ctx.db.createPhoneNumber({
                id: number,
                private: isPrivate,
                channelID: ctx.channel.id,
                guildID: ctx.guild.id
            });
            await ctx.send(await ctx.t("NUMBER_CREATED"));
        } else if (action === CallSymbol) {
            const [thisChannelNumber] = await ctx.db.getChannelPhoneNumbers(ctx.guild.id, ctx.channel.id);
            if (!thisChannelNumber) {
                await ctx.send(await ctx.t("CALLER_NO_NUMBER"));
                return;
            }
            const otherSideNumber = await ctx.db.getPhoneNumber(number);
            if (!otherSideNumber) {
                await ctx.send(await ctx.t("NUMBER_NONEXISTANT"));
                return;
            }

            await ctx.send(await ctx.t("CALLING"));

            try {
                await this.sosamba.createMessage(otherSideNumber.channelID,
                    `Incoming call by ${thisChannelNumber.id} ${!thisChannelNumber.private ? `(#${ctx.channel.name} at ${ctx.guild.name})` : ""}\nType \`${config.prefix}pickup\` to pick up the call. Else, type \`${config.prefix}hangup\`. You have 2 minutes to pick up the call, else the call will be automatically hung up.`);
                try {
                    const answer = await ctx.waitForAnyMessage(otherSideNumber.channelID,
                        ctx => (ctx.msg.content === `${config.prefix}pickup`
                            || ctx.msg.content === `${config.prefix}hangup`),
                        2 * 60e3);

                    if (answer.msg.content === `${config.prefix}pickup`) {
                        this.speakerPhone.addChannels(ctx.channel, this.sosamba.getChannel(otherSideNumber.channelID), thisChannelNumber, otherSideNumber);
                    } else {
                        for (const cID of [otherSideNumber.channelID, ctx.channel.id]) {
                            await this.sosamba.createMessage(cID, "The call was hung up.");
                        }
                    }
                } catch (err) {
                    console.error(err);
                    for (const cID of [otherSideNumber.channelID, ctx.channel.id]) {
                        await this.sosamba.createMessage(cID, "The call timed out.");
                    }
                }
            } catch {
                if (!this.sosamba.guilds.has(otherSideNumber.guildID)) {
                    await ctx.send(await ctx.t("CALL_ABORTED_BOT_REMOVED"));
                    const phoneNumbers = await ctx.db.getGuildPhoneNumbers(otherSideNumber.guildID);
                    await Promise.all(phoneNumbers.map(async ({ id }) => ctx.db.deletePhoneNumber(id)));
                    return;
                } else {
                    await ctx.send(await ctx.t("CALL_ABORTED_NO_PERMISSIONS"));
                    return;
                }

            }
        } else if (action === LookupSymbol) {
            const data = await ctx.db.getPhoneNumber(number);

            if (!data) {
                await ctx.send(await ctx.t("NUMBER_NONEXISTANT"));
                return;
            }

            const fields = [{
                name: await ctx.t("PRIVATE_NUMBER"),
                value: data.private ? await ctx.t("YES")
                    : await ctx.t("NO"),
                inline: true
            }];

            const isOnServer = this.sosamba.guilds.has(data.guildID);
            if (!isOnServer) {
                const phoneNumbersInDelGuild = await ctx.db.getGuildPhoneNumbers(data.guildID);
                await Promise.all(phoneNumbersInDelGuild.map(async ({ id }) => ctx.db.deletePhoneNumber(id)));
                console.log(`Deleted the phone numbers from ${data.guildID}: ${phoneNumbersInDelGuild.map(d => d.id).join(", ")}`);
            }

            const c = this.sosamba.getChannel(data.channelID);
            if (!data.private || !isOnServer) fields.push({
                name: await ctx.t("CHANNEL_INFORMATION"),
                value: isOnServer ? await ctx.t("CHANNEL_INFORMATION_VALUE", c, {
                    name: this.sosamba.escapeMarkdown(c.guild.name)
                }) : await ctx.t("NUMBER_AVAILABLE"),
                inline: true
            });

            await ctx.send({
                embed: {
                    author: {
                        name: await ctx.t("NUMBER_INFORMATION", data.id)
                    },
                    fields,
                    color: 0x008800
                }
            });
        } else if (action === DeleteSymbol) {
            const phone = ctx.db.getPhoneNumber(number);
            if (!phone || (!isOwner(ctx)
                && (phone && (phone.guildID !== ctx.guild.id)))
            ) {
                await ctx.send(await ctx.t("NUMBER_NONEXISTANT"));
                return;
            }
            await ctx.send(await ctx.t("QUESTION_DELETE_NUM", number));

            if (await ctx.askYesNo()) {
                await ctx.db.deletePhoneNumber(number);
                await ctx.send(await ctx.t("NUMBER_DELETED"));
            } else {
                await ctx.send(await ctx.t("OP_CANCELLED"));
            }
        }
    }
}

module.exports = PhoneCommand;<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */
"use strict";

const Interceptor = require('kronos-interceptor').Interceptor;
const parserFactory = require('./line-header');

/**
 * This interceptor cares about the handling of the messages.
 * It will add the hops and copies the messages
 */
class LineHeaderInterceptor extends Interceptor {

	constructor(endpoint, config) {
		super(endpoint, config);
	}

	static get type() {
		return "line-header";
	}

	get type() {
		return "line-header";
	}

	receive(request, oldRequest) {
		if (request.payload) {
			const streamFilter = parserFactory(this.config, true);
			const stream = request.payload;
			request.payload = stream.pipe(streamFilter);
		}
		return this.connected.receive(request, oldRequest);
	}
}

exports.Interceptor = LineHeaderInterceptor;
<commit_msg>perf(validation): Moved the validation of the config into the contractor, so it will be validated only once<commit_after>/* jslint node: true, esnext: true */
"use strict";

const Interceptor = require('kronos-interceptor').Interceptor;
const parserFactory = require('./line-header');

/**
 * This interceptor cares about the handling of the messages.
 * It will add the hops and copies the messages
 */
class LineHeaderInterceptor extends Interceptor {

	constructor(endpoint, config) {
		super(endpoint, config);

		// just validate the config once
		parserFactory(config, true);
	}

	static get type() {
		return "line-header";
	}

	get type() {
		return "line-header";
	}

	receive(request, oldRequest) {
		if (request.payload) {
			const streamFilter = parserFactory(this.config);
			const stream = request.payload;
			request.payload = stream.pipe(streamFilter);
		}
		return this.connected.receive(request, oldRequest);
	}
}

exports.Interceptor = LineHeaderInterceptor;
<|endoftext|>


<commit_before>"use strict";

import { ToInteger, defaultCompareFunction } from "./spec";
import { isNumberKey, isArrayBuffer, isArrayLike } from "./is";
import { createPrivateStorage } from "./private";

import memoize from "lodash-es/memoize";

import { roundToFloat16Bits, convertNumber } from "./lib";

const _ = createPrivateStorage();

function isFloat16Array(target) {
    return target instanceof Float16Array;
}

function assertFloat16Array(target) {
    if(!isFloat16Array(target)) {
        throw new TypeError("This is not a Float16Array");
    }
}

function copyToArray(float16bits) {
    const length = float16bits.length;

    const array = new Array(length);
    for(let i = 0; i < length; ++i) {
        array[i] = convertNumber(float16bits[i]);
    }

    return array;
}

// proxy handler
const handler = {
    get(target, key) {
        if(isNumberKey(key)) {
            return convertNumber( Reflect.get(target, key) );
        } else {
            const ret = Reflect.get(target, key);

            if(typeof ret !== "function")
                return ret;

            // TypedArray methods can't be called by Proxy
            let proxy = _(ret).proxy;

            if(proxy === undefined) {
                proxy = _(ret).proxy = new Proxy(ret, {
                    apply(func, thisArg, args) {
                        if(!isFloat16Array(thisArg))
                            return Reflect.apply(func, thisArg, args);

                        // peel off proxy
                        return Reflect.apply(func, _(thisArg).target, args);
                    }
                });
            }

            return proxy;
        }
    },

    set(target, key, value) {
        if(isNumberKey(key)) {
            Reflect.set(target, key, roundToFloat16Bits(value));
        } else {
            Reflect.set(target, key, value);
        }
    }
}

export default class Float16Array extends Uint16Array {

    constructor(input, byteOffset, length) {

        // input Float16Array
        if(isFloat16Array(input)) {
            super(_(input).target);

        // 22.2.1.3, 22.2.1.4 TypedArray, Array, ArrayLike, Iterable
        } else if(input !== null && typeof input === "object" && !isArrayBuffer(input)) {
            // if input is Iterable, get Array
            const array = isArrayLike(input) ? input : [...input];

            const length = array.length;
            super(length);

            for(let i = 0; i < length; ++i) {
                // super (Uint16Array)
                this[i] = roundToFloat16Bits( array[i] );
            }

        // 22.2.1.2, 22.2.1.5 primitive, ArrayBuffer
        } else {
            super(input, byteOffset, length);
        }

        const proxy = new Proxy(this, handler);

        // proxy private storage
        _(proxy).target = this;

        // this private storage
        _(this).proxy = proxy;

        return proxy;
    }

    // static methods
    static from(src, ...opts) {
        if(opts.length === 0)
            return new Float16Array( Uint16Array.from(src, roundToFloat16Bits).buffer );

        const mapFunc = opts[0];
        const thisArg = opts[1];

        return new Float16Array( Uint16Array.from(src, function(val, ...args) {
            return roundToFloat16Bits( mapFunc.call(this, val, ...args) );
        }, thisArg).buffer );
    }

    static of(...args) {
        return new this(args);
    }

    // iterate methods
    * [Symbol.iterator]() {
        for(const val of super[Symbol.iterator]()) {
            yield convertNumber(val);
        }
    }

    // keys

    * values() {
        for(const val of super.values()) {
            yield convertNumber(val);
        }
    }

    * entries() {
        for(const [i, val] of super.entries()) {
            yield [i, convertNumber(val)];
        }
    }

    // functional methods
    map(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        const array = [];
        for(let i = 0, l = this.length; i < l; ++i) {
            const val = convertNumber(this[i]);
            array.push( callback.call(thisArg, val, i, _(this).proxy) );
        }

        return new Float16Array(array);
    }

    filter(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        const array = [];
        for(let i = 0, l = this.length; i < l; ++i) {
            const val = convertNumber(this[i]);

            if( callback.call(thisArg, val, i, _(this).proxy) )
                array.push(val);
        }

        return new Float16Array(array);
    }

    reduce(callback, ...opts) {
        assertFloat16Array(this);

        let val, start;

        if(opts.length === 0) {
            val = convertNumber(this[0]);
            start = 1;
        } else {
            val = opts[0];
            start = 0;
        }

        for(let i = start, l = this.length; i < l; ++i) {
            val = callback(val, convertNumber(this[i]), i, _(this).proxy);
        }

        return val;
    }

    reduceRight(callback, ...opts) {
        assertFloat16Array(this);

        let val, start;

        const length = this.length;
        if(opts.length === 0) {
            val = convertNumber(this[length - 1]);
            start = length - 1;
        } else {
            val = opts[0];
            start = length;
        }

        for(let i = start; i--; ) {
            val = callback(val, convertNumber(this[i]), i, _(this).proxy);
        }

        return val;
    }

    forEach(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            callback.call(thisArg, convertNumber(this[i]), i, _(this).proxy);
        }
    }

    find(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            const value = convertNumber(this[i]);
            if( callback.call(thisArg, value, i, _(this).proxy) ) return value;
        }
    }

    findIndex(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            const value = convertNumber(this[i]);
            if( callback.call(thisArg, value, i, _(this).proxy) ) return i;
        }

        return -1;
    }

    every(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            if( !callback.call(thisArg, convertNumber(this[i]), i, _(this).proxy) ) return false;
        }

        return true;
    }

    some(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            if( callback.call(thisArg, convertNumber(this[i]), i, _(this).proxy) ) return true;
        }

        return false;
    }

    // change element methods
    set(input, offset = 0) {
        assertFloat16Array(this);

        let float16bits;

        // input Float16Array
        if(isFloat16Array(input)) {
            float16bits = _(input).target;

        // input others
        } else {
            const array = isArrayLike(input) ? input : [...input];
            const length = array.length;

            float16bits = new Uint16Array(length);
            for(let i = 0, l = array.length; i < l; ++i) {
                float16bits[i] = roundToFloat16Bits(array[i]);
            }
        }

        super.set(float16bits, offset);
    }

    reverse() {
        assertFloat16Array(this);

        super.reverse();

        return _(this).proxy;
    }

    fill(value, ...opts) {
        assertFloat16Array(this);

        super.fill(roundToFloat16Bits(value), ...opts);

        return _(this).proxy;
    }

    copyWithin(target, start, ...opts) {
        assertFloat16Array(this);

        super.copyWithin(target, start, ...opts);

        return _(this).proxy;
    }

    sort(...opts) {
        assertFloat16Array(this);

        let compareFunction = opts[0];

        if(compareFunction === undefined) {
            compareFunction = defaultCompareFunction;
        }

        const _convertNumber = memoize(convertNumber);

        super.sort((x, y) => compareFunction(_convertNumber(x), _convertNumber(y)));

        return _(this).proxy;
    }

    // copy element methods
    slice(...opts) {
        assertFloat16Array(this);

        // const float16bits = super.slice(...opts);

        const uint16 = new Uint16Array( this.buffer, this.byteOffset, this.length );
        const float16bits = uint16.slice(...opts);

        return new Float16Array( float16bits.buffer );
    }

    subarray(...opts) {
        assertFloat16Array(this);

        const float16bits = super.subarray(...opts);
        return new Float16Array( float16bits.buffer, float16bits.byteOffset, float16bits.length );
    }

    // contains methods
    indexOf(element, ...opts) {
        assertFloat16Array(this);

        const length = this.length;

        let from = ToInteger(opts[0]);

        if(from < 0) {
            from += length;
            if(from < 0)
                from = 0;
        }

        for(let i = from, l = length; i < l; ++i) {
            if(convertNumber(this[i]) === element)
                return i;
        }

        return -1;
    }

    lastIndexOf(element, ...opts) {
        assertFloat16Array(this);

        const length = this.length;

        let from = ToInteger(opts[0]);

        from = from === 0 ? length : from + 1;

        if(from >= 0) {
            from = from < length ? from : length;
        } else {
            from += length;
        }

        for(let i = from; i--; ) {
            if(convertNumber(this[i]) === element)
                return i;
        }

        return -1;
    }

    includes(element, ...opts) {
        assertFloat16Array(this);

        const length = this.length;

        let from = ToInteger(opts[0]);

        if(from < 0) {
            from += length;
            if(from < 0)
                from = 0;
        }

        const isNaN = Number.isNaN(element);
        for(let i = from, l = length; i < l; ++i) {
            const value = convertNumber(this[i]);

            if(isNaN && Number.isNaN(value))
                return true;

            if(value === element)
                return true;
        }

        return false;
    }

    // string methods
    join(...opts) {
        assertFloat16Array(this);

        const array = copyToArray(this);

        return array.join(...opts);
    }

    toLocaleString(...opts) {
        assertFloat16Array(this);

        const array = copyToArray(this);

        return array.toLocaleString(...opts);
    }

    toString() {
        const array = copyToArray(this);

        return array.toString();
    }

    get [Symbol.toStringTag]() {
        if(isFloat16Array(this))
            return "Float16Array";
    }

}<commit_msg>update for Firefox & Safari supports<commit_after>"use strict";

import { ToInteger, defaultCompareFunction } from "./spec";
import { isNumberKey, isArrayBuffer, isArrayLike } from "./is";
import { createPrivateStorage } from "./private";

import memoize from "lodash-es/memoize";

import { roundToFloat16Bits, convertNumber } from "./lib";

const isTypedArrayIndexedPropertyWritable = Object.getOwnPropertyDescriptor(new Uint8Array(1), 0).writable;

const _ = createPrivateStorage();

function isFloat16Array(target) {
    return target instanceof Float16Array;
}

function assertFloat16Array(target) {
    if(!isFloat16Array(target)) {
        throw new TypeError("This is not a Float16Array");
    }
}

function copyToArray(float16bits) {
    const length = float16bits.length;

    const array = new Array(length);
    for(let i = 0; i < length; ++i) {
        array[i] = convertNumber(float16bits[i]);
    }

    return array;
}

// proxy handler
const handler = {
    get(target, key) {
        // JavaScriptCore bug: https://bugs.webkit.org/show_bug.cgi?id=171606
        if(!isTypedArrayIndexedPropertyWritable) {
            target = _(target).target;
        }

        if(isNumberKey(key)) {
            return convertNumber( Reflect.get(target, key) );

        } else {
            const ret = Reflect.get(target, key);

            if(typeof ret !== "function")
                return ret;

            // TypedArray methods can't be called by Proxy
            let proxy = _(ret).proxy;

            if(proxy === undefined) {
                proxy = _(ret).proxy = new Proxy(ret, {
                    apply(func, thisArg, args) {
                        if(!isFloat16Array(thisArg))
                            return Reflect.apply(func, thisArg, args);

                        // peel off proxy
                        return Reflect.apply(func, _(thisArg).target, args);
                    }
                });
            }

            return proxy;
        }
    },

    set(target, key, value) {
        if(isNumberKey(key)) {
            Reflect.set(target, key, roundToFloat16Bits(value));

        } else {
            Reflect.set(target, key, value);
        }
    }
};

if(!isTypedArrayIndexedPropertyWritable) {
    handler.getPrototypeOf = target => Reflect.getPrototypeOf( _(target).target );
    handler.setPrototypeOf = (target, prototype) => Reflect.setPrototypeOf( _(target).target, prototype );
    handler.isExtensible = target => Reflect.isExtensible( _(target).target );
    handler.preventExtensions = target => Reflect.preventExtensions( _(target).target );
    handler.getOwnPropertyDescriptor = (target, key) => Reflect.getOwnPropertyDescriptor( _(target).target, key );
    handler.defineProperty = (target, key, descriptor) => Reflect.defineProperty( _(target).target, key, descriptor );
    handler.deleteProperty = (target, key) => Reflect.deleteProperty( _(target).target, key );
    handler.has = (target, key) => Reflect.has( _(target).target, key );
    handler.ownKeys = target => Reflect.ownKeys( _(target).target );
}

export default class Float16Array extends Uint16Array {

    constructor(input, byteOffset, length) {

        // input Float16Array
        if(isFloat16Array(input)) {
            super(_(input).target);

        // 22.2.1.3, 22.2.1.4 TypedArray, Array, ArrayLike, Iterable
        } else if(input !== null && typeof input === "object" && !isArrayBuffer(input)) {
            // if input is Iterable, get Array
            const array = isArrayLike(input) ? input : [...input];

            const length = array.length;
            super(length);

            for(let i = 0; i < length; ++i) {
                // super (Uint16Array)
                this[i] = roundToFloat16Bits( array[i] );
            }

        // 22.2.1.2, 22.2.1.5 primitive, ArrayBuffer
        } else {
            switch(arguments.length) {
                case 0:
                    super();
                    break;

                case 1:
                    super(input);
                    break;

                case 2:
                    super(input, byteOffset);
                    break;

                default:
                    super(input, byteOffset, length);
            }
        }

        let proxy;

        // JavaScriptCore bug: https://bugs.webkit.org/show_bug.cgi?id=171606
        if(isTypedArrayIndexedPropertyWritable) {
            proxy = new Proxy(this, handler);
        } else {
            const wrapper = Object.create(null);
            _(wrapper).target = this;
            proxy = new Proxy(wrapper, handler);
        }

        // proxy private storage
        _(proxy).target = this;

        // this private storage
        _(this).proxy = proxy;

        return proxy;
    }

    // static methods
    static from(src, ...opts) {
        if(opts.length === 0)
            return new Float16Array( Uint16Array.from(src, roundToFloat16Bits).buffer );

        const mapFunc = opts[0];
        const thisArg = opts[1];

        return new Float16Array( Uint16Array.from(src, function(val, ...args) {
            return roundToFloat16Bits( mapFunc.call(this, val, ...args) );
        }, thisArg).buffer );
    }

    static of(...args) {
        return new Float16Array(args);
    }

    // iterate methods
    * [Symbol.iterator]() {
        for(const val of super[Symbol.iterator]()) {
            yield convertNumber(val);
        }
    }

    // keys

    * values() {
        for(const val of super.values()) {
            yield convertNumber(val);
        }
    }

    * entries() {
        for(const [i, val] of super.entries()) {
            yield [i, convertNumber(val)];
        }
    }

    // functional methods
    map(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        const array = [];
        for(let i = 0, l = this.length; i < l; ++i) {
            const val = convertNumber(this[i]);
            array.push( callback.call(thisArg, val, i, _(this).proxy) );
        }

        return new Float16Array(array);
    }

    filter(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        const array = [];
        for(let i = 0, l = this.length; i < l; ++i) {
            const val = convertNumber(this[i]);

            if( callback.call(thisArg, val, i, _(this).proxy) )
                array.push(val);
        }

        return new Float16Array(array);
    }

    reduce(callback, ...opts) {
        assertFloat16Array(this);

        let val, start;

        if(opts.length === 0) {
            val = convertNumber(this[0]);
            start = 1;
        } else {
            val = opts[0];
            start = 0;
        }

        for(let i = start, l = this.length; i < l; ++i) {
            val = callback(val, convertNumber(this[i]), i, _(this).proxy);
        }

        return val;
    }

    reduceRight(callback, ...opts) {
        assertFloat16Array(this);

        let val, start;

        const length = this.length;
        if(opts.length === 0) {
            val = convertNumber(this[length - 1]);
            start = length - 1;
        } else {
            val = opts[0];
            start = length;
        }

        for(let i = start; i--; ) {
            val = callback(val, convertNumber(this[i]), i, _(this).proxy);
        }

        return val;
    }

    forEach(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            callback.call(thisArg, convertNumber(this[i]), i, _(this).proxy);
        }
    }

    find(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            const value = convertNumber(this[i]);
            if( callback.call(thisArg, value, i, _(this).proxy) ) return value;
        }
    }

    findIndex(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            const value = convertNumber(this[i]);
            if( callback.call(thisArg, value, i, _(this).proxy) ) return i;
        }

        return -1;
    }

    every(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            if( !callback.call(thisArg, convertNumber(this[i]), i, _(this).proxy) ) return false;
        }

        return true;
    }

    some(callback, ...opts) {
        assertFloat16Array(this);

        const thisArg = opts[0];

        for(let i = 0, l = this.length; i < l; ++i) {
            if( callback.call(thisArg, convertNumber(this[i]), i, _(this).proxy) ) return true;
        }

        return false;
    }

    // change element methods
    set(input, offset = 0) {
        assertFloat16Array(this);

        let float16bits;

        // input Float16Array
        if(isFloat16Array(input)) {
            float16bits = _(input).target;

        // input others
        } else {
            const array = isArrayLike(input) ? input : [...input];
            const length = array.length;

            float16bits = new Uint16Array(length);
            for(let i = 0, l = array.length; i < l; ++i) {
                float16bits[i] = roundToFloat16Bits(array[i]);
            }
        }

        super.set(float16bits, offset);
    }

    reverse() {
        assertFloat16Array(this);

        super.reverse();

        return _(this).proxy;
    }

    fill(value, ...opts) {
        assertFloat16Array(this);

        super.fill(roundToFloat16Bits(value), ...opts);

        return _(this).proxy;
    }

    copyWithin(target, start, ...opts) {
        assertFloat16Array(this);

        super.copyWithin(target, start, ...opts);

        return _(this).proxy;
    }

    sort(...opts) {
        assertFloat16Array(this);

        let compareFunction = opts[0];

        if(compareFunction === undefined) {
            compareFunction = defaultCompareFunction;
        }

        const _convertNumber = memoize(convertNumber);

        super.sort((x, y) => compareFunction(_convertNumber(x), _convertNumber(y)));

        return _(this).proxy;
    }

    // copy element methods
    slice(...opts) {
        assertFloat16Array(this);

        let float16bits;

        // V8, SpiderMonkey, JavaScriptCore throw TypeError
        try {
            float16bits = super.slice(...opts);
        } catch(e) {
            if(e instanceof TypeError) {
                const uint16 = new Uint16Array( this.buffer, this.byteOffset, this.length );
                float16bits = uint16.slice(...opts);
            } else {
                throw e;
            }
        }

        return new Float16Array( float16bits.buffer );
    }

    subarray(...opts) {
        assertFloat16Array(this);

        let float16bits;

        // SpiderMonkey, JavaScriptCore throw TypeError
        try {
            float16bits = super.subarray(...opts);
        } catch(e) {
            if(e instanceof TypeError) {
                const uint16 = new Uint16Array( this.buffer, this.byteOffset, this.length );
                float16bits = uint16.subarray(...opts);
            } else {
                throw e;
            }
        }

        return new Float16Array( float16bits.buffer, float16bits.byteOffset, float16bits.length );
    }

    // contains methods
    indexOf(element, ...opts) {
        assertFloat16Array(this);

        const length = this.length;

        let from = ToInteger(opts[0]);

        if(from < 0) {
            from += length;
            if(from < 0)
                from = 0;
        }

        for(let i = from, l = length; i < l; ++i) {
            if(convertNumber(this[i]) === element)
                return i;
        }

        return -1;
    }

    lastIndexOf(element, ...opts) {
        assertFloat16Array(this);

        const length = this.length;

        let from = ToInteger(opts[0]);

        from = from === 0 ? length : from + 1;

        if(from >= 0) {
            from = from < length ? from : length;
        } else {
            from += length;
        }

        for(let i = from; i--; ) {
            if(convertNumber(this[i]) === element)
                return i;
        }

        return -1;
    }

    includes(element, ...opts) {
        assertFloat16Array(this);

        const length = this.length;

        let from = ToInteger(opts[0]);

        if(from < 0) {
            from += length;
            if(from < 0)
                from = 0;
        }

        const isNaN = Number.isNaN(element);
        for(let i = from, l = length; i < l; ++i) {
            const value = convertNumber(this[i]);

            if(isNaN && Number.isNaN(value))
                return true;

            if(value === element)
                return true;
        }

        return false;
    }

    // string methods
    join(...opts) {
        assertFloat16Array(this);

        const array = copyToArray(this);

        return array.join(...opts);
    }

    toLocaleString(...opts) {
        assertFloat16Array(this);

        const array = copyToArray(this);

        return array.toLocaleString(...opts);
    }

    toString() {
        const array = copyToArray(this);

        return array.toString();
    }

    get [Symbol.toStringTag]() {
        if(isFloat16Array(this))
            return "Float16Array";
    }

}<|endoftext|>