<commit_before>// Copyright (c) 2012 Justin Greene
// NinajS - A mini html5 canvas javascript framework for multi touch drag and drop.
// MIT License

var NinjaJS = {};

NinjaJS.Stage = (function(){
    var getUserPositions = function(stage, e){
        var pos = stage.getContainerPosition();
        var touches = _.map(e.touches, function(touch){
            var touchX = touch.clientX - pos.left + window.pageXOffset;
            var touchY = touch.clientY - pos.top + window.pageYOffset;

            return {
                x: touchX,
                y: touchY,
                identifier: touch.identifier
            };
        });

        if(touches.length > 0){
            return touches;
        }

        var mouseX = e.clientX - pos.left + window.pageXOffset;
        var mouseY = e.clientY - pos.top + window.pageYOffset;

        return [{
            x: mouseX,
            y: mouseY,
            identifier: 'mouse'
        }];
    };

    var addListeners = function(stage){

        var dragStart = function(e){
            e.preventDefault();

            var touches = getUserPositions(stage, e);

            var usedTouches = [];

            for(var i = stage.shapes.length - 1; i >= 0; i--){
                var shape = stage.shapes[i];

                var touch = _.first(_.filter(touches, function(t){
                    return shape.isPointInPath(t.x, t.y);
                }));

                if(touch == null || _.include(usedTouches, touch)) { continue; }

                var isDragging = shape.isDragging();
                shape.dragPoint = touch;

                usedTouches.push(touch);

                if(!isDragging && shape.isDragging()){
                    e.userPosition = touch;
                    shape.trigger('dragstart', e);
                }
            }
        };

        var dragMove = function(e){
            e.preventDefault();
            var touches = getUserPositions(stage, e);

            var updateStage = false;

            _.each(stage.shapes, function(shape){
                if(!shape.isDragging()){ return; }

                var touch = _.find(touches, function(t){
                    return t.identifier === shape.dragPoint.identifier;
                });

                if(touch == null) { return; }

                var diffX = shape.dragX ? touch.x - shape.dragPoint.x : 0;
                var diffY = shape.dragY ? touch.y - shape.dragPoint.y : 0;

                if(diffX != 0 || diffY != 0){
                    shape.move(diffX, diffY);
                    e.userPosition = touch;
                    shape.isMoving = true;
                    e.draggedX = diffX;
                    e.draggedY = diffY;
                    shape.trigger('dragmove', e);
                    shape.dragPoint = touch;

                    updateStage = true;
                }

            });

            if(updateStage)
                stage.draw();

        };

        var dragEnd = function(e){
            e.preventDefault();

            var touches = getUserPositions(stage, e);

            _.each(stage.shapes, function(shape){
                if(!shape.isDragging()){ return; }

                var touch = _.find(touches, function(t){
                    return t.identifier === shape.dragPoint.identifier;
                });

                if(!e.touches || touch == null){
                    e.userPosition = shape.dragPoint;
                    shape.dragPoint = null;
                    shape.isMoving = false;
                    shape.trigger('dragend', e);
                }
            });
        };

        var mouseOver = function(e){

            var touches = getUserPositions(stage, e);

            _.each(stage.shapes, function(shape){
                var hasTouch = _.any(touches, function(t){
                    return shape.isPointInPath(t.x, t.y);
                });

                if(hasTouch){
                    shape.isMousedOver = true;
                    shape.trigger('mouseover', e);
                }else if(shape.isMousedOver && !hasTouch){
                    shape.isMousedOver = false;
                    shape.trigger('mouseout', e);
                }
            });
        };

         var createInputEvent = function(eventName){
            return function(e){
                e.preventDefault();
                var touches = getUserPositions(stage, e);

                _.each(stage.shapes, function(shape){

                    if(shape.draggable) { return; }

                    var touch = _.first(_.filter(touches, function(t){
                        return shape.isPointInPath(t.x, t.y);
                    }));

                    if(touch == null){ return; }

                    e.userPosition = touch;

                    shape.trigger(eventName, e);
                });
            };
        };

        var tapStart = function(e){
            var touches = getUserPositions(stage, e);

            _.each(stage.shapes, function(shape){
                if(shape.isMoving){ return; }

                var touch = _.first(_.filter(touches, function(t){
                    return shape.isPointInPath(t.x, t.y);
                }));

                if(touch == null) { return; }

                if(!shape.taps){
                    shape.taps = [];
                }
                shape.taps.push(touch);

                setTimeout(function(){
                    shape.taps = [];
                }, 300);
            });

        }

        var tapMove = function(e){
            e.preventDefault();
            _.each(stage.shapes, function(shape){
                shape.taps = [];
            });
        }

        var tapEnd = function(e){
            e.preventDefault();
            _.each(stage.shapes, function(shape){
                if(!shape.taps){ return; }
                if(shape.taps.length > 0){
                    e.taps = shape.taps;
                    e.userPosition = _.last(e.taps);
                    shape.trigger('tap', e);
                }
            });
        }

        stage.canvas.addEventListener('touchstart', dragStart, false);
        stage.canvas.addEventListener('touchmove', dragMove, false);
        stage.canvas.addEventListener('touchend', dragEnd, false);

        stage.canvas.addEventListener('mousedown', dragStart, false);
        stage.canvas.addEventListener('mousemove', dragMove, false);
        stage.canvas.addEventListener('mouseup', dragEnd, false);

        stage.canvas.addEventListener('touchstart', createInputEvent('inputstart'), false);
        stage.canvas.addEventListener('touchmove', createInputEvent('inputmove'), false);
        stage.canvas.addEventListener('touchend', createInputEvent('inputend'), false);

        stage.canvas.addEventListener('mousedown', createInputEvent('inputstart'), false);
        stage.canvas.addEventListener('mousemove', createInputEvent('inputmove'), false);
        stage.canvas.addEventListener('mouseup', createInputEvent('inputend'), false);

        stage.canvas.addEventListener('touchstart', tapStart, false);
        stage.canvas.addEventListener('touchmove', tapMove, false);
        stage.canvas.addEventListener('touchend', tapEnd, false);

        stage.canvas.addEventListener('mousedown', tapStart, false);
        stage.canvas.addEventListener('mousemove', tapMove, false);
        stage.canvas.addEventListener('mouseup', tapEnd, false);

        stage.canvas.addEventListener('mousemove', mouseOver, false);
    };

    return function(id, width, height){
        var self = this;
        this.container = document.getElementById(id);
        this.canvas = document.createElement('canvas');
        this.canvas.width = width;
        this.canvas.height = height;
        this.context = this.canvas.getContext('2d');
        this.width = width;
        this.height = height;
        this.shapes = [];
        this.maxZ = 0;

        this.container.appendChild(this.canvas);

        this.add = function(shape){
            shape.stage = self;
            shape.z = self.maxZ + 1;
            self.maxZ = shape.z;
            this.shapes.push(shape);
        };

        this.reorder = function(){
            self.shapes.sort(function(a, b){
                return a.z - b.z;
            });
        }

        this.getContainerPosition = function(){
            var obj = self.container;
            var top = 0;
            var left = 0;
            while (obj && obj.tagName != "BODY") {
                top += obj.offsetTop;
                left += obj.offsetLeft;
                obj = obj.offsetParent;
            }
            return {
                top: top,
                left: left
            };
        };

        this.clear = function(){
            var context = self.context;
            var canvas = self.canvas;
            context.clearRect(0, 0, canvas.width, canvas.height);
        };

        this.draw = function(){
            setTimeout(function(){
                self.clear();
                self.reorder();
                self.shapes.forEach(function(s){ s.draw(); });
            }, 0);
        }

        addListeners(self);

    };
})();

NinjaJS.Shape = (function(){

    return function(drawFunc){
        var self = this;
        this.visible = true;
        this.drawFunc = drawFunc;
        this.x = 0;
        this.y = 0;
        this.z = 0;
        this.taps = 0;
        this.events = {};
        this.dragPoint = null;
        this.draggable = false;
        this.dragX = true;
        this.dragY = true;

        this.isDragging = function(){
            if(self.draggable === false)
                return false;

            return self.dragPoint != null;
        }

        this.getCanvas = function(){
            if(!self.canvas)
                self.canvas = document.createElement('canvas');
            return self.canvas;
        };

        this.getContext = function(){
            if(!self.context)
                self.context = self.canvas.getContext('2d');
            return self.context;
        }

        this.redraw = function(){
            this.canvas = document.createElement('canvas');
            this.context = self.canvas.getContext('2d');
            this.drawFunc.call(self);
        }

        this.draw = function(){
            if(!self.visible){ return; }

            var context = self.stage.context;
            var cachedCanvas = self.getCanvas();
            context.save();

            if (self.x !== 0 || self.y !== 0) {
                context.translate(self.x, self.y);
            }

            context.drawImage(cachedCanvas, 0, 0, cachedCanvas.width, cachedCanvas.height, 0, 0, cachedCanvas.width, cachedCanvas.height);
            context.restore();
        }

        this.bind = function(eventName, func){
            var e = self.events[eventName];
            if(!e){
                e = [];
                self.events[eventName] = e;
            }
            e.push(func);
        }

        this.trigger = function(eventName, e){
            var events = self.events[eventName];
            if(events){
                for(var i = 0; i < events.length; i++){
                    events[i](e);
                }
            }
        }

        this.unbind = function(eventName){
            var e = self.events[eventName];
            if(e){
                delete self.events[eventName];
            }
        }

        this.setPosition = function(x,y){
            self.x = x;
            self.y = y;
        }

        this.isPointInPath = function(x,y){
            var normalized = { x: x - this.x, y: y - this.y }

            return self.getContext().isPointInPath(normalized.x, normalized.y);
        }

        this.moveToTop = function(){
            self.z = self.stage.maxZ + 1;
            self.stage.maxZ = self.z;
            self.stage.reorder();
        }

        this.move = function(x,y){
            self.x += x;
            self.y += y;
        }

        this.redraw();
    }
})();<commit_msg>various improvements<commit_after>// Copyright (c) 2012 Justin Greene
// NinajS - A mini html5 canvas javascript framework
// for multi touch drag and drop.
// MIT License

var NinjaJS = (function() {
    var getUserPositions = function(stage, e) {
        var pos = stage.getContainerPosition();
        var touches = _.map(e.touches, function(touch) {
            var touchX = (touch.clientX - pos.left + window.pageXOffset) * stage.resizeRatio;
            var touchY = (touch.clientY - pos.top + window.pageYOffset) * stage.resizeRatio;

            return {
                x: touchX,
                y: touchY,
                identifier: touch.identifier
            };
        });

        if (touches.length > 0) {
            return touches;
        }

        var mouseX = (e.clientX - pos.left + window.pageXOffset) * stage.resizeRatio;
        var mouseY = (e.clientY - pos.top + window.pageYOffset) * stage.resizeRatio;

        return [{
            x: mouseX,
            y: mouseY,
            identifier: 'mouse'
        }];
    };

    var addListeners = function(stage) {

        var dragStart = function(e) {
            e.preventDefault();

            var touches = getUserPositions(stage, e);
            var usedTouches = [];
            for (var i = stage.shapes.length - 1; i >= 0; i--) {
                var shape = stage.shapes[i];

                var touch = _.first(_.filter(touches, function(t) {
                    return shape.isPointInPath(t.x, t.y);
                }));

                if (touch == null || _.include(usedTouches, touch)) { continue; }

                var isDragging = shape.isDragging();
                shape.dragPoint = touch;

                usedTouches.push(touch);

                if (!isDragging && shape.isDragging()) {
                    e.userPosition = touch;
                    shape.trigger('dragstart', e);
                }
            }
        };

        var dragMove = function(e) {
            e.preventDefault();
            var touches = getUserPositions(stage, e);

            var updateStage = false;

            _.each(stage.shapes, function(shape) {
                if (!shape.isDragging()) { return; }

                var touch = _.find(touches, function(t) {
                    return t.identifier === shape.dragPoint.identifier;
                });

                if (touch == null) { return; }

                var diffX = shape.dragX ? touch.x - shape.dragPoint.x : 0;
                var diffY = shape.dragY ? touch.y - shape.dragPoint.y : 0;

                if (diffX != 0 || diffY != 0) {
                    shape.move(diffX, diffY);
                    e.userPosition = touch;
                    shape.isMoving = true;
                    e.draggedX = diffX;
                    e.draggedY = diffY;
                    shape.trigger('dragmove', e);
                    shape.dragPoint = touch;

                    updateStage = true;
                }

            });

            if (updateStage)
                stage.draw();

        };

        var dragEnd = function(e) {
            e.preventDefault();

            var touches = getUserPositions(stage, e);

            _.each(stage.shapes, function(shape) {
                if (!shape.isDragging()) { return; }

                var touch = _.find(touches, function(t) {
                    return t.identifier === shape.dragPoint.identifier;
                });

                if (!e.touches || touch == null) {
                    e.userPosition = shape.dragPoint;
                    shape.dragPoint = null;
                    shape.isMoving = false;
                    shape.trigger('dragend', e);
                }
            });
        };

        var mouseOver = function(e) {

            var touches = getUserPositions(stage, e);

            _.each(stage.shapes, function(shape) {
                var hasTouch = _.any(touches, function(t) {
                    return shape.isPointInPath(t.x, t.y);
                });

                if (hasTouch) {
                    shape.isMousedOver = true;
                    shape.trigger('mouseover', e);
                }else if (shape.isMousedOver && !hasTouch) {
                    shape.isMousedOver = false;
                    shape.trigger('mouseout', e);
                }
            });
        };

         var createInputEvent = function(eventName) {
            return function(e) {
                e.preventDefault();
                var touches = getUserPositions(stage, e);

                _.each(stage.shapes, function(shape) {

                    if (shape.draggable) { return; }

                    var touch = _.first(_.filter(touches, function(t) {
                        return shape.isPointInPath(t.x, t.y);
                    }));

                    if (touch == null) { return; }

                    e.userPosition = touch;

                    shape.trigger(eventName, e);
                });
            };
        };

        var tapStart = function(e) {
            e.preventDefault();
            var touches = getUserPositions(stage, e);

            var usedTouches = [];

            for (var i = stage.shapes.length - 1; i >= 0; i--) {
                (function(i) {
                    var shape = stage.shapes[i];

                    if (shape.isMoving) { return; }

                    var touch = _.first(_.filter(touches, function(t) {
                        return shape.isPointInPath(t.x, t.y);
                    }));

                    if (touch == null || _.include(usedTouches, touch)) { return; }

                    if (!shape.taps) {
                        shape.taps = [];
                    }
                    shape.taps.push(touch);

                    usedTouches.push(touch);

                    setTimeout(function() {
                        shape.taps = [];
                    }, 300);

                })(i);
            }

        }

        var tapMove = function(e) {
            e.preventDefault();
            _.each(stage.shapes, function(shape) {
                shape.taps = [];
            });
        }

        var tapEnd = function(e) {
            e.preventDefault();
            _.each(stage.shapes, function(shape) {
                if (!shape.taps) { return; }
                if (shape.taps.length > 0) {
                    e.taps = shape.taps;
                    e.userPosition = _.last(e.taps);
                    shape.trigger('tap', e);
                }
            });
        }

        var click = function(e) {
            e.preventDefault();

            var touches = getUserPositions(stage, e);

            _.each(stage.shapes, function(shape) {
                if (shape.isMoving) { return; }

                var touch = _.first(_.filter(touches, function(t) {
                    return shape.isPointInPath(t.x, t.y);
                }));

                if (touch == null) { return; }

                e.userPosition = touch;

                shape.trigger('click', e);

            });
        }

        stage.canvas.addEventListener('touchstart', dragStart, false);
        stage.canvas.addEventListener('touchmove', dragMove, false);
        stage.canvas.addEventListener('touchend', dragEnd, false);

        stage.canvas.addEventListener('mousedown', dragStart, false);
        stage.canvas.addEventListener('mousemove', dragMove, false);
        stage.canvas.addEventListener('mouseup', dragEnd, false);

        stage.canvas.addEventListener('touchstart', createInputEvent('inputstart'), false);
        stage.canvas.addEventListener('touchmove', createInputEvent('inputmove'), false);
        stage.canvas.addEventListener('touchend', createInputEvent('inputend'), false);

        stage.canvas.addEventListener('mousedown', createInputEvent('inputstart'), false);
        stage.canvas.addEventListener('mousemove', createInputEvent('inputmove'), false);
        stage.canvas.addEventListener('mouseup', createInputEvent('inputend'), false);

        stage.canvas.addEventListener('touchstart', tapStart, false);
        stage.canvas.addEventListener('touchmove', tapMove, false);
        stage.canvas.addEventListener('touchend', tapEnd, false);

        stage.canvas.addEventListener('mousedown', tapStart, false);
        stage.canvas.addEventListener('mousemove', tapMove, false);
        stage.canvas.addEventListener('mouseup', tapEnd, false);

        stage.canvas.addEventListener('mousemove', mouseOver, false);

        stage.canvas.addEventListener('click', click, false);
    };

    var getResizeRatio = function(dim, maxWidth, maxHeight) {

        var ratioX = maxWidth / dim.width;
        var ratioY = maxHeight / dim.height;
        return (function() {
            if (ratioX <= ratioY) {
                return ratioX;
            }

            return ratioY;
        })();
    }

    var getResizeDimensions = function(dim, maxWidth, maxHeight) {

        var ratio = getResizeRatio(dim, maxWidth, maxHeight);

        return { width: Math.round(dim.width * ratio), height: Math.round(dim.height * ratio) };
    };

    var removeItem = function(arr) {
        var what, a = arguments, L = a.length, ax;
        while (L > 1 && arr.length) {
            what = a[--L];
            while ((ax = arr.indexOf(what)) != -1) {
                arr.splice(ax, 1);
            }
        }
        return arr;
    };

    var Stage = (function() {

        return function(elem, canvasWidth, canvasHeight, actualWidth, actualHeight) {
            var self = this;
            this.container = elem;
            this.canvas = document.createElement('canvas');
            this.canvas.width = canvasWidth;
            this.canvas.height = canvasHeight;
            this.context = this.canvas.getContext('2d');
            this.width = canvasWidth;
            this.height = canvasHeight;
            this.shapes = [];
            this.maxZ = 0;
            this.resizeRatio = 1;

            this.container.appendChild(this.canvas);

            this.add = function(shape) {
                shape.stage = self;
                shape.z = self.maxZ + 1;
                self.maxZ = shape.z;
                this.shapes.push(shape);
            };

            this.remove = function(shape) {
                removeItem(this.shapes, shape);
            };

            this.reorder = function() {
                self.shapes.sort(function(a, b) {
                    return a.z - b.z;
                });
            };

            this.getContainerPosition = function() {
                var _x = 0;
                var _y = 0;
                var el = self.canvas;
                while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
                    _x += el.offsetLeft - el.scrollLeft;
                    _y += el.offsetTop - el.scrollTop;
                    el = el.offsetParent;
                }
                return { top: _y, left: _x };
            };

            this.clear = function() {
                var context = self.context;
                var canvas = self.canvas;
                context.clearRect(0, 0, canvas.width, canvas.height);
            };

            this.draw = function() {
                setTimeout(function() {
                    self.clear();
                    self.reorder();
                    self.shapes.forEach(function(s) { s.draw(); });
                }, 0);
            };

            this.resizeCanvas = function(w, h) {
                var dim = getResizeDimensions({ width: self.canvas.width, height: self.canvas.height }, w, h);

                self.resizeRatio = 1 / getResizeRatio({ width: self.canvas.width, height: self.canvas.height }, w, h);

                self.canvas.style.width = dim.width + 'px';
                self.canvas.style.height = dim.height + 'px';

            }

            addListeners(self);

            if (actualWidth && actualHeight) {
                self.resizeCanvas(actualWidth, actualHeight);
            }

        };
    })();

    var Shape = (function() {

        return function(drawFunc) {
            var self = this;
            this.visible = true;
            this.drawFunc = drawFunc;
            this.x = 0;
            this.y = 0;
            this.z = 0;
            this.scale = 1;
            this.taps = 0;
            this.events = {};
            this.dragPoint = null;
            this.draggable = false;
            this.dragX = true;
            this.dragY = true;
            this.rotation = 0;

            this.isDragging = function() {
                if (self.draggable === false)
                    return false;

                return self.dragPoint != null;
            };

            this.getCanvas = function() {
                if (!self.canvas)
                    self.canvas = document.createElement('canvas');
                return self.canvas;
            };

            this.getContext = function() {
                if (!self.context)
                    self.context = self.canvas.getContext('2d');
                return self.context;
            };

            this.redraw = function() {
                this.canvas = document.createElement('canvas');
                this.context = self.canvas.getContext('2d');
                this.drawFunc.call(self);
            };

            this.draw = function() {
                if (!self.visible) { return; }

                var context = self.stage.context;
                var cachedCanvas = self.getCanvas();
                context.save();

                if (self.x !== 0 || self.y !== 0) {
                    context.translate(self.x, self.y);
                }

                context.drawImage(cachedCanvas, 0, 0, cachedCanvas.width, cachedCanvas.height, 0, 0, cachedCanvas.width, cachedCanvas.height);
                context.restore();

            };

            this.animateTo = (function() {
                var isAnimating = false;
                return function(endX, endY, onMove) {
                    var shape = self;
                    if (isAnimating === true) { return; }

                    var distanceX = endX - shape.x;
                    var distanceY = endY - shape.y;

                    if (distanceX === 0 && distanceY === 0) { return; }

                    var divisor = 4;

                    var distanceXToMove = distanceX < 0 ? Math.ceil(distanceX / divisor) : Math.floor(distanceX / divisor);

                    var distanceYToMove = distanceY < 0 ? Math.ceil(distanceY / divisor) : Math.floor(distanceY / divisor);

                    if (distanceXToMove === 0) {
                        distanceXToMove = distanceX;
                    }

                    if (distanceYToMove === 0) {
                        distanceYToMove = distanceY;
                    }

                    isAnimating = true;

                    setTimeout(function() {
                        isAnimating = false;
                        shape.move(distanceXToMove, distanceYToMove);

                        if (onMove) {
                            onMove();
                        }

                        self.stage.draw();

                        self.animateTo(endX, endY, onMove);

                    }, 1000 / 60);
                };
            })();

            this.bind = function(eventName, func) {
                var e = self.events[eventName];
                if (!e) {
                    e = [];
                    self.events[eventName] = e;
                }
                e.push(func);
            }

            this.trigger = function(eventName, e) {
                var events = self.events[eventName];
                if (events) {
                    for (var i = 0; i < events.length; i++) {
                        events[i](e);
                    }
                }
            }

            this.unbind = function(eventName) {
                var e = self.events[eventName];
                if (e) {
                    delete self.events[eventName];
                }
            }

            this.setPosition = function(x, y) {
                self.x = x;
                self.y = y;
            }

            this.isPointInPath = function(x, y) {
                var normalized = { x: x - this.x, y: y - this.y };

                //var ratio = 1 / self.stage.resizeRatio;

                //var normalized = { x: x * ratio - this.x, y: y * ratio - this.y };

                return self.getContext().isPointInPath(normalized.x, normalized.y);
            }

            this.moveToTop = function() {
                self.z = self.stage.maxZ + 1;
                self.stage.maxZ = self.z;
                self.stage.reorder();
            }

            this.move = function(x, y) {
                self.x += x;
                self.y += y;
            }

            this.redraw();
        }
    })();

    return {
        Stage: Stage,
        Shape: Shape
    };
})();
<|endoftext|>


<commit_before>CodeMirror.defineMode("riscv", function(config, parserConfig) {
    function regexFromWords(words, ins) {
        return new RegExp("^(?:" + words.join("|") + ")$", ins);
    }

    var instructions = regexFromWords([
        "add",
        "addi",
        "and",
        "andi",
        "auipc",
        "beq",
        "bge",
        "bgeu",
        "blt",
        "bltu",
        "bne",
        "div",
        "divu",
        "ecall",
        "jal",
        "jalr",
        "lb",
        "lbu",
        "lh",
        "lhu",
        "lui",
        "lw",
        "mul",
        "mulh",
        "mulhsu",
        "mulhu",
        "or",
        "ori",
        "rem",
        "remu",
        "sb",
        "sh",
        "slli",
        "slt",
        "slti",
        "sltiu",
        "sltu",
        "srai",
        "srli",
        "sub",
        "sw",
        "xor",
        "xori",
        /* pseudoinstructions */
        "j",
        "jr",
        "la",
        "li",
        "mv",
        "nop",
        "ret",
        /* nonstandard pseudoinstructions */
        "seq",
        "sge",
        "sgt",
        "sle",
        "sne"
    ], "i");

    var registers = regexFromWords([
        "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
        "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30", "x31",
        "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
        "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
    ], "");

    var keywords = regexFromWords([
        ".data", ".text", ".globl", ".float", ".double",
        ".asciiz", ".word", ".byte"
    ], "i");

    function normal(stream, state) {
        var ch = stream.next();

        if (ch == "#") {
            stream.skipToEnd();
            return "comment";
        }

        if (ch == "\"" || ch == "'") {
            state.cur = string(ch)
            return state.cur(stream, state);
        }

        if (/\d/.test(ch)) {
            stream.eatWhile(/[\w.%]/);
            return "number";
        }

        if (/[.\w_]/.test(ch)) {
            stream.eatWhile(/[\w\\\-_.]/);
            return "variable";
        }

        return null;
    }

    function string(quote) {
        return function(stream, state) {
            var escaped = false, ch;
            while ((ch = stream.next()) != null) {
                if (ch == quote && !escaped) break;
                escaped = !escaped && ch == "\\";
            }
            if (!escaped) state.cur = normal;
            return "string";
        };
    }

    return {
        startState: function(basecol) {
            return { basecol: basecol || 0, indentDepth: 0, cur: normal };
        },

        token: function(stream, state) {
            if (stream.eatSpace()) return null;
            var style = state.cur(stream, state);
            var word = stream.current();
            if (style == "variable") {
                if (keywords.test(word)) style = "keyword";
                else if (instructions.test(word)) style = "builtin";
                else if (registers.test(word)) style = "variable-2";
            }
            return style;
        }
    };
});
<commit_msg>Add new instructions missing from syntax highlighter<commit_after>CodeMirror.defineMode("riscv", function(config, parserConfig) {
    function regexFromWords(words, ins) {
        return new RegExp("^(?:" + words.join("|") + ")$", ins);
    }

    var instructions = regexFromWords([
        "add",
        "addi",
        "and",
        "andi",
        "auipc",
        "beq",
        "bge",
        "bgeu",
        "blt",
        "bltu",
        "bne",
        "div",
        "divu",
        "ecall",
        "jal",
        "jalr",
        "lb",
        "lbu",
        "lh",
        "lhu",
        "lui",
        "lw",
        "mul",
        "mulh",
        "mulhsu",
        "mulhu",
        "or",
        "ori",
        "rem",
        "remu",
        "sb",
        "sh",
        "sll",
        "slli",
        "slt",
        "slti",
        "sltiu",
        "sltu",
        "srai",
        "srl",
        "srli",
        "sub",
        "sw",
        "xor",
        "xori",
        /* pseudoinstructions */
        "j",
        "jr",
        "la",
        "li",
        "mv",
        "nop",
        "ret",
        /* nonstandard pseudoinstructions */
        "seq",
        "sge",
        "sgt",
        "sle",
        "sne"
    ], "i");

    var registers = regexFromWords([
        "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
        "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30", "x31",
        "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
        "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
    ], "");

    var keywords = regexFromWords([
        ".data", ".text", ".globl", ".float", ".double",
        ".asciiz", ".word", ".byte"
    ], "i");

    function normal(stream, state) {
        var ch = stream.next();

        if (ch == "#") {
            stream.skipToEnd();
            return "comment";
        }

        if (ch == "\"" || ch == "'") {
            state.cur = string(ch)
            return state.cur(stream, state);
        }

        if (/\d/.test(ch)) {
            stream.eatWhile(/[\w.%]/);
            return "number";
        }

        if (/[.\w_]/.test(ch)) {
            stream.eatWhile(/[\w\\\-_.]/);
            return "variable";
        }

        return null;
    }

    function string(quote) {
        return function(stream, state) {
            var escaped = false, ch;
            while ((ch = stream.next()) != null) {
                if (ch == quote && !escaped) break;
                escaped = !escaped && ch == "\\";
            }
            if (!escaped) state.cur = normal;
            return "string";
        };
    }

    return {
        startState: function(basecol) {
            return { basecol: basecol || 0, indentDepth: 0, cur: normal };
        },

        token: function(stream, state) {
            if (stream.eatSpace()) return null;
            var style = state.cur(stream, state);
            var word = stream.current();
            if (style == "variable") {
                if (keywords.test(word)) style = "keyword";
                else if (instructions.test(word)) style = "builtin";
                else if (registers.test(word)) style = "variable-2";
            }
            return style;
        }
    };
});
<|endoftext|>


<commit_before>CodeMirror.defineMode("riscv", function(config, parserConfig) {
    function regexFromWords(words, ins) {
        return new RegExp("^(?:" + words.join("|") + ")$", ins);
    }

    var instructions = regexFromWords([
        "add",
        "addi",
        "and",
        "andi",
        "auipc",
        "beq",
        "bge",
        "bgeu",
        "blt",
        "bltu",
        "bne",
        "div",
        "divu",
        "ecall",
        "jal",
        "jalr",
        "lb",
        "lbu",
        "lh",
        "lhu",
        "lui",
        "lw",
        "mul",
        "mulh",
        "mulhsu",
        "mulhu",
        "or",
        "ori",
        "rem",
        "remu",
        "sb",
        "sh",
        "sll",
        "slli",
        "slt",
        "slti",
        "sltiu",
        "sltu",
        "srai",
        "srl",
        "srli",
        "sub",
        "sw",
        "xor",
        "xori",
        /* pseudoinstructions */
        "beqz",
        "bgez",
        "bgt",
        "bgtu",
        "bgtz",
        "ble",
        "bleu",
        "blez",
        "bltz",
        "bnez",
        "call",
        "jal",
        "jalr",
        "j",
        "jr",
        "la",
        "lb",
        "lbu",
        "lh",
        "lhu",
        "li",
        "lw",
        "mv",
        "neg",
        "nop",
        "not",
        "ret",
        "sb",
        "seqz",
        "sgtz",
        "sh",
        "sltz",
        "snez",
        "sw",
        "tail",

    ], "i");

    var registers = regexFromWords([
        "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
        "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30", "x31",
        "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
        "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
    ], "");

    var keywords = regexFromWords([
        ".data", ".text", ".globl", ".float", ".double",
        ".asciiz", ".word", ".byte"
    ], "i");

    function normal(stream, state) {
        var ch = stream.next();

        if (ch == "#") {
            stream.skipToEnd();
            return "comment";
        }

        if (ch == "\"" || ch == "'") {
            state.cur = string(ch)
            return state.cur(stream, state);
        }

        if (/\d/.test(ch)) {
            stream.eatWhile(/[\w.%]/);
            return "number";
        }

        if (/[.\w_]/.test(ch)) {
            stream.eatWhile(/[\w\\\-_.]/);
            return "variable";
        }

        return null;
    }

    function string(quote) {
        return function(stream, state) {
            var escaped = false, ch;
            while ((ch = stream.next()) != null) {
                if (ch == quote && !escaped) break;
                escaped = !escaped && ch == "\\";
            }
            if (!escaped) state.cur = normal;
            return "string";
        };
    }

    return {
        startState: function(basecol) {
            return { basecol: basecol || 0, indentDepth: 0, cur: normal };
        },

        token: function(stream, state) {
            if (stream.eatSpace()) return null;
            var style = state.cur(stream, state);
            var word = stream.current();
            if (style == "variable") {
                if (keywords.test(word)) style = "keyword";
                else if (instructions.test(word)) style = "builtin";
                else if (registers.test(word)) style = "variable-2";
            }
            return style;
        }
    };
});

CodeMirror.registerHelper("lint", "riscv", function (text) {
    var errors = [];
    var parseError = function(err) {
        var line = err.lineNumber;
        errors.push({from: CodeMirror.Pos(line - 1, 0),
                     to: CodeMirror.Pos(line, 0),
                     severity: "error",
                     message: err.message});
    };

    var res = window.venus_main.venus.assembler.Linter.lint(text);
    for (var i = 0; i < res.length; i++) {
        parseError(res[i]);
    }
    return errors;
});
<commit_msg>Remove extra comma<commit_after>CodeMirror.defineMode("riscv", function(config, parserConfig) {
    function regexFromWords(words, ins) {
        return new RegExp("^(?:" + words.join("|") + ")$", ins);
    }

    var instructions = regexFromWords([
        "add",
        "addi",
        "and",
        "andi",
        "auipc",
        "beq",
        "bge",
        "bgeu",
        "blt",
        "bltu",
        "bne",
        "div",
        "divu",
        "ecall",
        "jal",
        "jalr",
        "lb",
        "lbu",
        "lh",
        "lhu",
        "lui",
        "lw",
        "mul",
        "mulh",
        "mulhsu",
        "mulhu",
        "or",
        "ori",
        "rem",
        "remu",
        "sb",
        "sh",
        "sll",
        "slli",
        "slt",
        "slti",
        "sltiu",
        "sltu",
        "srai",
        "srl",
        "srli",
        "sub",
        "sw",
        "xor",
        "xori",
        /* pseudoinstructions */
        "beqz",
        "bgez",
        "bgt",
        "bgtu",
        "bgtz",
        "ble",
        "bleu",
        "blez",
        "bltz",
        "bnez",
        "call",
        "jal",
        "jalr",
        "j",
        "jr",
        "la",
        "lb",
        "lbu",
        "lh",
        "lhu",
        "li",
        "lw",
        "mv",
        "neg",
        "nop",
        "not",
        "ret",
        "sb",
        "seqz",
        "sgtz",
        "sh",
        "sltz",
        "snez",
        "sw",
        "tail"
    ], "i");

    var registers = regexFromWords([
        "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
        "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30", "x31",
        "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
        "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
    ], "");

    var keywords = regexFromWords([
        ".data", ".text", ".globl", ".float", ".double",
        ".asciiz", ".word", ".byte"
    ], "i");

    function normal(stream, state) {
        var ch = stream.next();

        if (ch == "#") {
            stream.skipToEnd();
            return "comment";
        }

        if (ch == "\"" || ch == "'") {
            state.cur = string(ch)
            return state.cur(stream, state);
        }

        if (/\d/.test(ch)) {
            stream.eatWhile(/[\w.%]/);
            return "number";
        }

        if (/[.\w_]/.test(ch)) {
            stream.eatWhile(/[\w\\\-_.]/);
            return "variable";
        }

        return null;
    }

    function string(quote) {
        return function(stream, state) {
            var escaped = false, ch;
            while ((ch = stream.next()) != null) {
                if (ch == quote && !escaped) break;
                escaped = !escaped && ch == "\\";
            }
            if (!escaped) state.cur = normal;
            return "string";
        };
    }

    return {
        startState: function(basecol) {
            return { basecol: basecol || 0, indentDepth: 0, cur: normal };
        },

        token: function(stream, state) {
            if (stream.eatSpace()) return null;
            var style = state.cur(stream, state);
            var word = stream.current();
            if (style == "variable") {
                if (keywords.test(word)) style = "keyword";
                else if (instructions.test(word)) style = "builtin";
                else if (registers.test(word)) style = "variable-2";
            }
            return style;
        }
    };
});

CodeMirror.registerHelper("lint", "riscv", function (text) {
    var errors = [];
    var parseError = function(err) {
        var line = err.lineNumber;
        errors.push({from: CodeMirror.Pos(line - 1, 0),
                     to: CodeMirror.Pos(line, 0),
                     severity: "error",
                     message: err.message});
    };

    var res = window.venus_main.venus.assembler.Linter.lint(text);
    for (var i = 0; i < res.length; i++) {
        parseError(res[i]);
    }
    return errors;
});
<|endoftext|>


<commit_before>/**
 * This is a BTree based index, which is generally stored in memory.
 * It is used by transactions.
 * @implements {IIndex}
 */
class InMemoryIndex {
    /**
     * Creates a new InMemoryIndex for a given object store.
     * The key path describes the path of the secondary key within the stored objects.
     * Only objects for which the key path exists are part of the secondary index.
     *
     * A key path is defined by a key within the object or alternatively a path through the object to a specific subkey.
     * For example, ['a', 'b'] could be used to use 'key' as the key in the following object:
     * { 'a': { 'b': 'key' } }
     *
     * If a secondary index is a multi entry index, and the value at the key path is iterable,
     * every item of the iterable value will be associated with the object.
     * @param {IObjectStore} objectStore The underlying object store to use.
     * @param {string|Array.<string>} [keyPath] The key path of the indexed attribute.
     * If the keyPath is not given, this is a primary index.
     * @param {boolean} [multiEntry] Whether the indexed attribute is considered to be iterable or not.
     * @param {boolean} [unique] Whether there is a unique constraint on the attribute.
     */
    constructor(objectStore, keyPath, multiEntry=false, unique=false) {
        this._objectStore = objectStore;
        this._keyPath = keyPath;
        this._multiEntry = multiEntry;
        this._unique = unique;
        this._tree = new BTree();
    }

    /**
     * Reinitialises the index.
     * @returns {Promise} The promise resolves after emptying the index.
     */
    async truncate() {
        this._tree = new BTree();
    }

    /**
     * Helper method to return the attribute associated with the key path if it exists.
     * @param {string} key The primary key of the key-value pair.
     * @param {*} obj The value of the key-value pair.
     * @returns {*} The attribute associated with the key path, if it exists, and undefined otherwise.
     * @private
     */
    _indexKey(key, obj) {
        if (this.keyPath) {
            if (obj === undefined) return undefined;
            return ObjectUtils.byKeyPath(obj, this.keyPath);
        }
        return key;
    }

    /**
     * The key path associated with this index.
     * A key path is defined by a key within the object or alternatively a path through the object to a specific subkey.
     * For example, ['a', 'b'] could be used to use 'key' as the key in the following object:
     * { 'a': { 'b': 'key' } }
     * If the keyPath is undefined, this index uses the primary key of the key-value store.
     * @type {string|Array.<string>}
     */
    get keyPath() {
        return this._keyPath;
    }

    /**
     * This value determines whether the index supports multiple secondary keys per entry.
     * If so, the value at the key path is considered to be an iterable.
     * @type {boolean}
     */
    get multiEntry() {
        return this._multiEntry;
    }

    /**
     * This value determines whether the index is a unique constraint.
     * @type {boolean}
     */
    get unique() {
        return this._unique;
    }

    /**
     * A helper method to insert a primary-secondary key pair into the tree.
     * @param {string} key The primary key.
     * @param {*} iKey The indexed key.
     * @param {IBTree} [tree] The optional tree in which to insert the pair.
     * @throws if the uniqueness constraint is violated.
     */
    _insert(key, iKey, tree) {
        tree = tree || this._tree;
        if (!this._multiEntry || !Array.isArray(iKey)) {
            iKey = [iKey];
        }
        // Add all keys.
        for (const component of iKey) {
            if (tree.seek(component)) {
                if (this._unique) {
                    throw new Error(`Uniqueness constraint violated for key ${key} on path ${this._keyPath}`);
                }
                tree.currentRecord.add(key);
            } else {
                tree.insert(component, this._unique ? key : Set.from(key));
            }
        }
    }

    /**
     * Inserts a new key-value pair into the index.
     * For replacing an existing pair, the old value has to be passed as well.
     * @param {string} key The primary key of the pair.
     * @param {*} value The value of the pair. The indexed key will be extracted from this.
     * @param {*} [oldValue] The old value associated with the primary key.
     * @returns {TreeTransaction} The TreeTransaction that was needed to insert/replace the key-value pair.
     */
    put(key, value, oldValue) {
        const treeTx = this._tree.transaction();
        const oldIKey = this._indexKey(key, oldValue);
        const newIKey = this._indexKey(key, value);

        if (oldIKey !== undefined) {
            this._remove(key, oldIKey, treeTx);
        }
        if (newIKey !== undefined) {
            this._insert(key, newIKey, treeTx);
        }
        return treeTx;
    }

    /**
     * Removes a key-value pair from the index.
     * @param {string} key The primary key of the pair.
     * @param {*} oldValue The old value of the pair. The indexed key will be extracted from this.
     * @returns {TreeTransaction} The TreeTransaction that was needed to remove the key-value pair.
     */
    remove(key, oldValue) {
        const treeTx = this._tree.transaction();
        const iKey = this._indexKey(key, oldValue);
        if (iKey !== undefined) {
            this._remove(key, iKey, treeTx);
        }
        return treeTx;
    }

    /**
     * A helper method to remove a primary-secondary key pair from the tree.
     * @param {string} key The primary key.
     * @param {*} iKey The indexed key.
     * @param {IBTree} [tree] The optional tree in which to insert the pair.
     */
    _remove(key, iKey, tree) {
        tree = tree || this._tree;
        if (!this._multiEntry || !Array.isArray(iKey)) {
            iKey = [iKey];
        }
        // Remove all keys.
        for (const component of iKey) {
            if (tree.seek(component)) {
                if (!this._unique && tree.currentRecord.size > 1) {
                    tree.currentRecord.delete(key);
                } else {
                    tree.remove(component);
                }
            }
        }
    }

    /**
     * A helper method to retrieve the values corresponding to a set of keys.
     * @param {Set.<string>} keys The set of keys to get the corresponding values for.
     * @returns {Promise.<Array.<*>>} A promise of the array of values.
     * @protected
     */
    async _retrieveValues(keys) {
        const valuePromises = [];
        for (const key of keys) {
            valuePromises.push(this._objectStore.get(key));
        }
        return Promise.all(valuePromises);
    }

    /**
     * Returns a promise of an array of objects whose secondary keys fulfill the given query.
     * If the optional query is not given, it returns all objects in the index.
     * If the query is of type KeyRange, it returns all objects whose secondary keys are within this range.
     * @param {KeyRange} [query] Optional query to check secondary keys against.
     * @returns {Promise.<Array.<*>>} A promise of the array of objects relevant to the query.
     */
    async values(query=null) {
        const keys = await this.keys(query);
        return this._retrieveValues(keys);
    }

    /**
     * Returns a promise of a set of primary keys, whose associated objects' secondary keys are in the given range.
     * If the optional query is not given, it returns all primary keys in the index.
     * If the query is of type KeyRange, it returns all primary keys for which the secondary key is within this range.
     * @param {KeyRange} [query] Optional query to check the secondary keys against.
     * @returns {Promise.<Set.<string>>} A promise of the set of primary keys relevant to the query.
     */
    async keys(query=null) {
        let resultSet = new Set();

        // Shortcut for exact match.
        if (query instanceof KeyRange && query.exactMatch) {
            if (this._tree.seek(query.lower)) {
                resultSet = Set.from(this._tree.currentRecord);
            }
            return resultSet;
        }

        // Find lower bound and start from there.
        if (!(query instanceof KeyRange)) {
            this._tree.goTop();
        } else {
            if (!this._tree.goToLowerBound(query.lower, query.lowerOpen)) {
                return resultSet; // empty
            }
        }

        while (!(query instanceof KeyRange) || query.includes(this._tree.currentKey)) {
            resultSet = resultSet.union(Set.from(this._tree.currentRecord));
            if (!this._tree.skip()) {
                break;
            }
        }
        return resultSet;
    }

    /**
     * Iterates over the keys in a given range and direction.
     * The callback is called for each primary key fulfilling the query
     * until it returns false and stops the iteration.
     * @param {function(key:string):boolean} callback A predicate called for each key until returning false.
     * @param {boolean} ascending Determines the direction of traversal.
     * @param {KeyRange} query An optional KeyRange to narrow down the iteration space.
     * @returns {Promise} The promise resolves after all elements have been streamed.
     */
    keyStream(callback, ascending=true, query=null) {
        if (!this._unique) {
            throw new Error('Unsupported operation for non-unique indices');
        }

        // Find lower bound and start from there.
        if (!(query instanceof KeyRange)) {
            if (ascending) {
                this._tree.goTop();
            } else {
                this._tree.goBottom();
            }
        } else {
            if (ascending) {
                if (!this._tree.goToLowerBound(query.lower, query.lowerOpen)) {
                    return Promise.resolve();
                }
            } else {
                if (!this._tree.goToUpperBound(query.upper, query.upperOpen)) {
                    return Promise.resolve();
                }
            }
        }

        while (!(query instanceof KeyRange) || query.includes(this._tree.currentKey)) {
            if (!callback(this._tree.currentRecord)) {
                break;
            }
            if (!this._tree.skip(ascending ? 1 : -1)) {
                break;
            }
        }
        return Promise.resolve();
    }

    /**
     * Iterates over the keys and values in a given range and direction.
     * The callback is called for each value and primary key fulfilling the query
     * until it returns false and stops the iteration.
     * @param {function(value:*, key:string):boolean} callback A predicate called for each value and key until returning false.
     * @param {boolean} ascending Determines the direction of traversal.
     * @param {KeyRange} query An optional KeyRange to narrow down the iteration space.
     * @returns {Promise} The promise resolved after all elements have been streamed.
     */
    async valueStream(callback, ascending=true, query=null) {
        if (!this._unique) {
            throw new Error('Unsupported operation for non-unique indices');
        }

        // Find lower bound and start from there.
        if (!(query instanceof KeyRange)) {
            if (ascending) {
                this._tree.goTop();
            } else {
                this._tree.goBottom();
            }
        } else {
            if (ascending) {
                if (!this._tree.goToLowerBound(query.lower, query.lowerOpen)) {
                    return;
                }
            } else {
                if (!this._tree.goToUpperBound(query.upper, query.upperOpen)) {
                    return;
                }
            }
        }

        while (!(query instanceof KeyRange) || query.includes(this._tree.currentKey)) {
            if (!callback(await this._objectStore.get(this._tree.currentRecord), this._tree.currentRecord)) {
                break;
            }
            if (!this._tree.skip(ascending ? 1 : -1)) {
                break;
            }
        }
    }

    /**
     * Returns a promise of an array of objects whose secondary key is maximal for the given range.
     * If the optional query is not given, it returns the objects whose secondary key is maximal within the index.
     * If the query is of type KeyRange, it returns the objects whose secondary key is maximal for the given range.
     * @param {KeyRange} [query] Optional query to check keys against.
     * @returns {Promise.<Array.<*>>} A promise of array of objects relevant to the query.
     */
    async maxValues(query=null) {
        const keys = await this.maxKeys(query);
        return this._retrieveValues(keys);
    }

    /**
     * Returns a promise of a set of primary keys, whose associated secondary keys are maximal for the given range.
     * If the optional query is not given, it returns the set of primary keys, whose associated secondary key is maximal within the index.
     * If the query is of type KeyRange, it returns the set of primary keys, whose associated secondary key is maximal for the given range.
     * @param {KeyRange} [query] Optional query to check keys against.
     * @returns {Promise.<Set.<*>>} A promise of the key relevant to the query.
     */
    async maxKeys(query=null) {
        const isRange = query instanceof KeyRange;
        if (!this._tree.goToUpperBound(isRange ? query.upper : undefined, isRange ? query.upperOpen : false)) {
            return new Set();
        }
        return Set.from(this._tree.currentRecord);
    }

    /**
     * Returns a promise of an array of objects whose secondary key is minimal for the given range.
     * If the optional query is not given, it returns the objects whose secondary key is minimal within the index.
     * If the query is of type KeyRange, it returns the objects whose secondary key is minimal for the given range.
     * @param {KeyRange} [query] Optional query to check keys against.
     * @returns {Promise.<Array.<*>>} A promise of array of objects relevant to the query.
     */
    async minValues(query=null) {
        const keys = await this.minKeys(query);
        return this._retrieveValues(keys);
    }

    /**
     * Returns a promise of a set of primary keys, whose associated secondary keys are minimal for the given range.
     * If the optional query is not given, it returns the set of primary keys, whose associated secondary key is minimal within the index.
     * If the query is of type KeyRange, it returns the set of primary keys, whose associated secondary key is minimal for the given range.
     * @param {KeyRange} [query] Optional query to check keys against.
     * @returns {Promise.<Set.<*>>} A promise of the key relevant to the query.
     */
    async minKeys(query=null) {
        const isRange = query instanceof KeyRange;
        if (!this._tree.goToLowerBound(isRange ? query.lower : undefined, isRange ? query.lowerOpen : false)) {
            return new Set();
        }
        return Set.from(this._tree.currentRecord);
    }

    /**
     * Returns the count of entries, whose secondary key is in the given range.
     * If the optional query is not given, it returns the count of entries in the index.
     * If the query is of type KeyRange, it returns the count of entries, whose secondary key is within the given range.
     * @param {KeyRange} [query]
     * @returns {Promise.<number>}
     */
    async count(query=null) {
        return (await this.keys(query)).size;
        // The code below does only work for unique indices.
        // if (!(query instanceof KeyRange)) {
        //     return this._tree.length;
        // }
        // if (!this._tree.goToLowerBound(query.lower, query.lowerOpen)) {
        //     return 0;
        // }
        // const start = this._tree.keynum();
        // if (!this._tree.goToUpperBound(query.upper, query.upperOpen)) {
        //     return 0;
        // }
        // const end = this._tree.keynum();
        // return end - start + 1;
    }
}
Class.register(InMemoryIndex);

<commit_msg>Minor performance improvement.<commit_after>/**
 * This is a BTree based index, which is generally stored in memory.
 * It is used by transactions.
 * @implements {IIndex}
 */
class InMemoryIndex {
    /**
     * Creates a new InMemoryIndex for a given object store.
     * The key path describes the path of the secondary key within the stored objects.
     * Only objects for which the key path exists are part of the secondary index.
     *
     * A key path is defined by a key within the object or alternatively a path through the object to a specific subkey.
     * For example, ['a', 'b'] could be used to use 'key' as the key in the following object:
     * { 'a': { 'b': 'key' } }
     *
     * If a secondary index is a multi entry index, and the value at the key path is iterable,
     * every item of the iterable value will be associated with the object.
     * @param {IObjectStore} objectStore The underlying object store to use.
     * @param {string|Array.<string>} [keyPath] The key path of the indexed attribute.
     * If the keyPath is not given, this is a primary index.
     * @param {boolean} [multiEntry] Whether the indexed attribute is considered to be iterable or not.
     * @param {boolean} [unique] Whether there is a unique constraint on the attribute.
     */
    constructor(objectStore, keyPath, multiEntry=false, unique=false) {
        this._objectStore = objectStore;
        this._keyPath = keyPath;
        this._multiEntry = multiEntry;
        this._unique = unique;
        this._tree = new BTree();
    }

    /**
     * Reinitialises the index.
     * @returns {Promise} The promise resolves after emptying the index.
     */
    async truncate() {
        this._tree = new BTree();
    }

    /**
     * Helper method to return the attribute associated with the key path if it exists.
     * @param {string} key The primary key of the key-value pair.
     * @param {*} obj The value of the key-value pair.
     * @returns {*} The attribute associated with the key path, if it exists, and undefined otherwise.
     * @private
     */
    _indexKey(key, obj) {
        if (this.keyPath) {
            if (obj === undefined) return undefined;
            return ObjectUtils.byKeyPath(obj, this.keyPath);
        }
        return key;
    }

    /**
     * The key path associated with this index.
     * A key path is defined by a key within the object or alternatively a path through the object to a specific subkey.
     * For example, ['a', 'b'] could be used to use 'key' as the key in the following object:
     * { 'a': { 'b': 'key' } }
     * If the keyPath is undefined, this index uses the primary key of the key-value store.
     * @type {string|Array.<string>}
     */
    get keyPath() {
        return this._keyPath;
    }

    /**
     * This value determines whether the index supports multiple secondary keys per entry.
     * If so, the value at the key path is considered to be an iterable.
     * @type {boolean}
     */
    get multiEntry() {
        return this._multiEntry;
    }

    /**
     * This value determines whether the index is a unique constraint.
     * @type {boolean}
     */
    get unique() {
        return this._unique;
    }

    /**
     * A helper method to insert a primary-secondary key pair into the tree.
     * @param {string} key The primary key.
     * @param {*} iKey The indexed key.
     * @param {IBTree} [tree] The optional tree in which to insert the pair.
     * @throws if the uniqueness constraint is violated.
     */
    _insert(key, iKey, tree) {
        tree = tree || this._tree;
        if (!this._multiEntry || !Array.isArray(iKey)) {
            iKey = [iKey];
        }
        // Add all keys.
        for (const component of iKey) {
            if (tree.seek(component)) {
                if (this._unique) {
                    throw new Error(`Uniqueness constraint violated for key ${key} on path ${this._keyPath}`);
                }
                tree.currentRecord.add(key);
            } else {
                tree.insert(component, this._unique ? key : Set.from(key));
            }
        }
    }

    /**
     * Inserts a new key-value pair into the index.
     * For replacing an existing pair, the old value has to be passed as well.
     * @param {string} key The primary key of the pair.
     * @param {*} value The value of the pair. The indexed key will be extracted from this.
     * @param {*} [oldValue] The old value associated with the primary key.
     * @returns {TreeTransaction} The TreeTransaction that was needed to insert/replace the key-value pair.
     */
    put(key, value, oldValue) {
        const treeTx = this._tree.transaction();
        const oldIKey = this._indexKey(key, oldValue);
        const newIKey = this._indexKey(key, value);

        if (oldIKey !== newIKey) {
            if (oldIKey !== undefined) {
                this._remove(key, oldIKey, treeTx);
            }
            if (newIKey !== undefined) {
                this._insert(key, newIKey, treeTx);
            }
        }
        return treeTx;
    }

    /**
     * Removes a key-value pair from the index.
     * @param {string} key The primary key of the pair.
     * @param {*} oldValue The old value of the pair. The indexed key will be extracted from this.
     * @returns {TreeTransaction} The TreeTransaction that was needed to remove the key-value pair.
     */
    remove(key, oldValue) {
        const treeTx = this._tree.transaction();
        const iKey = this._indexKey(key, oldValue);
        if (iKey !== undefined) {
            this._remove(key, iKey, treeTx);
        }
        return treeTx;
    }

    /**
     * A helper method to remove a primary-secondary key pair from the tree.
     * @param {string} key The primary key.
     * @param {*} iKey The indexed key.
     * @param {IBTree} [tree] The optional tree in which to insert the pair.
     */
    _remove(key, iKey, tree) {
        tree = tree || this._tree;
        if (!this._multiEntry || !Array.isArray(iKey)) {
            iKey = [iKey];
        }
        // Remove all keys.
        for (const component of iKey) {
            if (tree.seek(component)) {
                if (!this._unique && tree.currentRecord.size > 1) {
                    tree.currentRecord.delete(key);
                } else {
                    tree.remove(component);
                }
            }
        }
    }

    /**
     * A helper method to retrieve the values corresponding to a set of keys.
     * @param {Set.<string>} keys The set of keys to get the corresponding values for.
     * @returns {Promise.<Array.<*>>} A promise of the array of values.
     * @protected
     */
    async _retrieveValues(keys) {
        const valuePromises = [];
        for (const key of keys) {
            valuePromises.push(this._objectStore.get(key));
        }
        return Promise.all(valuePromises);
    }

    /**
     * Returns a promise of an array of objects whose secondary keys fulfill the given query.
     * If the optional query is not given, it returns all objects in the index.
     * If the query is of type KeyRange, it returns all objects whose secondary keys are within this range.
     * @param {KeyRange} [query] Optional query to check secondary keys against.
     * @returns {Promise.<Array.<*>>} A promise of the array of objects relevant to the query.
     */
    async values(query=null) {
        const keys = await this.keys(query);
        return this._retrieveValues(keys);
    }

    /**
     * Returns a promise of a set of primary keys, whose associated objects' secondary keys are in the given range.
     * If the optional query is not given, it returns all primary keys in the index.
     * If the query is of type KeyRange, it returns all primary keys for which the secondary key is within this range.
     * @param {KeyRange} [query] Optional query to check the secondary keys against.
     * @returns {Promise.<Set.<string>>} A promise of the set of primary keys relevant to the query.
     */
    async keys(query=null) {
        let resultSet = new Set();

        // Shortcut for exact match.
        if (query instanceof KeyRange && query.exactMatch) {
            if (this._tree.seek(query.lower)) {
                resultSet = Set.from(this._tree.currentRecord);
            }
            return resultSet;
        }

        // Find lower bound and start from there.
        if (!(query instanceof KeyRange)) {
            this._tree.goTop();
        } else {
            if (!this._tree.goToLowerBound(query.lower, query.lowerOpen)) {
                return resultSet; // empty
            }
        }

        while (!(query instanceof KeyRange) || query.includes(this._tree.currentKey)) {
            resultSet = resultSet.union(Set.from(this._tree.currentRecord));
            if (!this._tree.skip()) {
                break;
            }
        }
        return resultSet;
    }

    /**
     * Iterates over the keys in a given range and direction.
     * The callback is called for each primary key fulfilling the query
     * until it returns false and stops the iteration.
     * @param {function(key:string):boolean} callback A predicate called for each key until returning false.
     * @param {boolean} ascending Determines the direction of traversal.
     * @param {KeyRange} query An optional KeyRange to narrow down the iteration space.
     * @returns {Promise} The promise resolves after all elements have been streamed.
     */
    keyStream(callback, ascending=true, query=null) {
        if (!this._unique) {
            throw new Error('Unsupported operation for non-unique indices');
        }

        // Find lower bound and start from there.
        if (!(query instanceof KeyRange)) {
            if (ascending) {
                this._tree.goTop();
            } else {
                this._tree.goBottom();
            }
        } else {
            if (ascending) {
                if (!this._tree.goToLowerBound(query.lower, query.lowerOpen)) {
                    return Promise.resolve();
                }
            } else {
                if (!this._tree.goToUpperBound(query.upper, query.upperOpen)) {
                    return Promise.resolve();
                }
            }
        }

        while (!(query instanceof KeyRange) || query.includes(this._tree.currentKey)) {
            if (!callback(this._tree.currentRecord)) {
                break;
            }
            if (!this._tree.skip(ascending ? 1 : -1)) {
                break;
            }
        }
        return Promise.resolve();
    }

    /**
     * Iterates over the keys and values in a given range and direction.
     * The callback is called for each value and primary key fulfilling the query
     * until it returns false and stops the iteration.
     * @param {function(value:*, key:string):boolean} callback A predicate called for each value and key until returning false.
     * @param {boolean} ascending Determines the direction of traversal.
     * @param {KeyRange} query An optional KeyRange to narrow down the iteration space.
     * @returns {Promise} The promise resolved after all elements have been streamed.
     */
    async valueStream(callback, ascending=true, query=null) {
        if (!this._unique) {
            throw new Error('Unsupported operation for non-unique indices');
        }

        // Find lower bound and start from there.
        if (!(query instanceof KeyRange)) {
            if (ascending) {
                this._tree.goTop();
            } else {
                this._tree.goBottom();
            }
        } else {
            if (ascending) {
                if (!this._tree.goToLowerBound(query.lower, query.lowerOpen)) {
                    return;
                }
            } else {
                if (!this._tree.goToUpperBound(query.upper, query.upperOpen)) {
                    return;
                }
            }
        }

        while (!(query instanceof KeyRange) || query.includes(this._tree.currentKey)) {
            if (!callback(await this._objectStore.get(this._tree.currentRecord), this._tree.currentRecord)) {
                break;
            }
            if (!this._tree.skip(ascending ? 1 : -1)) {
                break;
            }
        }
    }

    /**
     * Returns a promise of an array of objects whose secondary key is maximal for the given range.
     * If the optional query is not given, it returns the objects whose secondary key is maximal within the index.
     * If the query is of type KeyRange, it returns the objects whose secondary key is maximal for the given range.
     * @param {KeyRange} [query] Optional query to check keys against.
     * @returns {Promise.<Array.<*>>} A promise of array of objects relevant to the query.
     */
    async maxValues(query=null) {
        const keys = await this.maxKeys(query);
        return this._retrieveValues(keys);
    }

    /**
     * Returns a promise of a set of primary keys, whose associated secondary keys are maximal for the given range.
     * If the optional query is not given, it returns the set of primary keys, whose associated secondary key is maximal within the index.
     * If the query is of type KeyRange, it returns the set of primary keys, whose associated secondary key is maximal for the given range.
     * @param {KeyRange} [query] Optional query to check keys against.
     * @returns {Promise.<Set.<*>>} A promise of the key relevant to the query.
     */
    async maxKeys(query=null) {
        const isRange = query instanceof KeyRange;
        if (!this._tree.goToUpperBound(isRange ? query.upper : undefined, isRange ? query.upperOpen : false)) {
            return new Set();
        }
        return Set.from(this._tree.currentRecord);
    }

    /**
     * Returns a promise of an array of objects whose secondary key is minimal for the given range.
     * If the optional query is not given, it returns the objects whose secondary key is minimal within the index.
     * If the query is of type KeyRange, it returns the objects whose secondary key is minimal for the given range.
     * @param {KeyRange} [query] Optional query to check keys against.
     * @returns {Promise.<Array.<*>>} A promise of array of objects relevant to the query.
     */
    async minValues(query=null) {
        const keys = await this.minKeys(query);
        return this._retrieveValues(keys);
    }

    /**
     * Returns a promise of a set of primary keys, whose associated secondary keys are minimal for the given range.
     * If the optional query is not given, it returns the set of primary keys, whose associated secondary key is minimal within the index.
     * If the query is of type KeyRange, it returns the set of primary keys, whose associated secondary key is minimal for the given range.
     * @param {KeyRange} [query] Optional query to check keys against.
     * @returns {Promise.<Set.<*>>} A promise of the key relevant to the query.
     */
    async minKeys(query=null) {
        const isRange = query instanceof KeyRange;
        if (!this._tree.goToLowerBound(isRange ? query.lower : undefined, isRange ? query.lowerOpen : false)) {
            return new Set();
        }
        return Set.from(this._tree.currentRecord);
    }

    /**
     * Returns the count of entries, whose secondary key is in the given range.
     * If the optional query is not given, it returns the count of entries in the index.
     * If the query is of type KeyRange, it returns the count of entries, whose secondary key is within the given range.
     * @param {KeyRange} [query]
     * @returns {Promise.<number>}
     */
    async count(query=null) {
        return (await this.keys(query)).size;
        // The code below does only work for unique indices.
        // if (!(query instanceof KeyRange)) {
        //     return this._tree.length;
        // }
        // if (!this._tree.goToLowerBound(query.lower, query.lowerOpen)) {
        //     return 0;
        // }
        // const start = this._tree.keynum();
        // if (!this._tree.goToUpperBound(query.upper, query.upperOpen)) {
        //     return 0;
        // }
        // const end = this._tree.keynum();
        // return end - start + 1;
    }
}
Class.register(InMemoryIndex);

<|endoftext|>


<commit_before>/*
 */
Ext.namespace('Gemma');

/**
 *
 * Grid to display ExpressionExperiments. Author: Paul (based on Luke's
 * CoexpressionDatasetGrid) $Id: ExpressionExperimentGrid.js,v 1.13 2008/04/23
 * 19:54:46 kelsey Exp $
 */
Gemma.ExpressionExperimentGrid = Ext.extend(Gemma.GemmaGridPanel, {

	/*
	 * Do not set header : true here - it breaks it.
	 */
	collapsible : false,
	readMethod : ExpressionExperimentController.loadExpressionExperiments
			.createDelegate(this, [], true),

	autoExpandColumn : 'name',

	editable : true,

	record : Ext.data.Record.create([{
		name : "id",
		type : "int"
	}, {
		name : "shortName",
		type : "string"
	}, {
		name : "name",
		type : "string"
	}, {
		name : "arrayDesignCount",
		type : "int"
	}, {
		name : "bioAssayCount",
		type : "int"
	}, {
		name : "externalUri",
		type : "string"
	}, {
		name : "description",
		type : "string"
	}]),

	initComponent : function() {
		if (this.pageSize) {
			if (this.records) {
				Ext.apply(this, {
					store : new Ext.data.Store({
						proxy : new Ext.ux.data.PagingMemoryProxy(this.records),
						reader : new Ext.data.ListRangeReader({}, this.record),
						pageSize : this.pageSize
					})
				});
			} else {
				Ext.apply(this, {
					store : new Gemma.PagingDataStore({
						proxy : new Ext.data.DWRProxy(this.readMethod),
						reader : new Ext.data.ListRangeReader({
							id : "id"
						}, this.record),
						pageSize : this.pageSize
					})
				});
			}
			Ext.apply(this, {
				bbar : new Gemma.PagingToolbar({
					pageSize : this.pageSize,
					store : this.store
				})
			});
		} else {
			if (!this.records) {
				Ext.apply(this, {
					store : new Ext.data.Store({
						proxy : new Ext.data.DWRProxy(this.readMethod),
						reader : new Ext.data.ListRangeReader({
							id : "id"
						}, this.record)
					})
				});
			} else {
				Ext.apply(this, {
					store : new Ext.data.Store({
						proxy : new Ext.data.MemoryProxy(this.records),
						reader : new Ext.data.ListRangeReader({}, this.record)
					})
				});
			}
		}

		Ext.apply(this, {
			columns : [{
				id : 'shortName',
				header : "Dataset",
				dataIndex : "shortName",
				renderer : this.formatEE,
		//		width : 80,
				sortable : true
			}, {
				id : 'name',
				header : "Name",
				dataIndex : "name",
			//	width : 120,
				sortable : true
			}, {
				id : 'arrays',
				header : "Arrays",
				dataIndex : "arrayDesignCount",
			//	width : 50,
				sortable : true
			}, {
				id : 'assays',
				header : "Assays",
				dataIndex : "bioAssayCount",
				renderer : this.formatAssayCount,
			//	width : 50,
				sortable : true
			}]
		});

		if (this.rowExpander) {
			Ext.apply(this, {
				rowExpander : new Gemma.EEGridRowExpander({
					tpl : ""
				})
			});
			this.columns.unshift(this.rowExpander);
			Ext.apply(this, {
				plugins : this.rowExpander
			});
		}

		Gemma.ExpressionExperimentGrid.superclass.initComponent.call(this);

		this.on("keypress", function(e) {
			if (e.getCharCode() == Ext.EventObject.DELETE) {
				this.removeSelected();
			}
		}, this);

		this.getStore().on("load", function() {
			this.doLayout();
		}, this);

	},

	afterRender : function() {
		Gemma.ExpressionExperimentGrid.superclass.afterRender.call(this);
		if (this.getTopToolbar()) {
			this.getTopToolbar().grid = this;
		}
	},

	removeSelected : function() {
		var recs = this.getSelectionModel().getSelections();
		for (var x = 0; x < recs.length; x++) { // for r in recs
			// does
			// not
			// work!
			this.getStore().remove(recs[x]);
			this.getView().refresh();
		}
	},

	formatAssayCount : function(value, metadata, record, row, col, ds) {
		return String
				.format(
						"{0} ",
						record.data.bioAssayCount, record.data.id);
	},

	formatEE : function(value, metadata, record, row, col, ds) {
		var eeTemplate = new Ext.Template("{shortName}");
		return eeTemplate.apply(record.data);
	},

	/**
	 * Return all the ids of the experiments shown in this grid.
	 */
	getEEIds : function() {
		var result = [];
		this.store.each(function(rec) {
			result.push(rec.get("id"));
		});
		return result;
	},

	isEditable : function() {
		return this.editable;
	},

	setEditable : function(b) {
		this.editable = b;
	}

});

Gemma.ExpressionExperimentGrid.updateDatasetInfo = function(datasets, eeMap) {
	for (var i = 0; i < datasets.length; ++i) {
		var ee = eeMap[datasets[i].id];
		if (ee) {
			datasets[i].shortName = ee.shortName;
			datasets[i].name = ee.name;
		}
	}
};

Gemma.EEGridRowExpander = Ext.extend(Ext.grid.RowExpander, {

	fillExpander : function(data, body, rowIndex) {
		Ext.DomHelper.overwrite(body, {
			tag : 'p',
			html : data
		});
	},

	beforeExpand : function(record, body, rowIndex) {
		ExpressionExperimentController.getDescription(record.id, {
			callback : this.fillExpander.createDelegate(this, [body, rowIndex],
					true)
		});
		return true;
	}

});
<commit_msg>remove assay magnifier. I've never been a fan of it.<commit_after>/*
 */
Ext.namespace('Gemma');

/**
 *
 * Grid to display ExpressionExperiments. Author: Paul (based on Luke's
 * CoexpressionDatasetGrid) $Id: ExpressionExperimentGrid.js,v 1.13 2008/04/23
 * 19:54:46 kelsey Exp $
 */
Gemma.ExpressionExperimentGrid = Ext.extend(Gemma.GemmaGridPanel, {

	/*
	 * Do not set header : true here - it breaks it.
	 */
	collapsible : false,
	readMethod : ExpressionExperimentController.loadExpressionExperiments
			.createDelegate(this, [], true),

	autoExpandColumn : 'name',

	editable : true,

	record : Ext.data.Record.create([{
		name : "id",
		type : "int"
	}, {
		name : "shortName",
		type : "string"
	}, {
		name : "name",
		type : "string"
	}, {
		name : "arrayDesignCount",
		type : "int"
	}, {
		name : "bioAssayCount",
		type : "int"
	}, {
		name : "externalUri",
		type : "string"
	}, {
		name : "description",
		type : "string"
	}]),

	initComponent : function() {
		if (this.pageSize) {
			if (this.records) {
				Ext.apply(this, {
					store : new Ext.data.Store({
						proxy : new Ext.ux.data.PagingMemoryProxy(this.records),
						reader : new Ext.data.ListRangeReader({}, this.record),
						pageSize : this.pageSize
					})
				});
			} else {
				Ext.apply(this, {
					store : new Gemma.PagingDataStore({
						proxy : new Ext.data.DWRProxy(this.readMethod),
						reader : new Ext.data.ListRangeReader({
							id : "id"
						}, this.record),
						pageSize : this.pageSize
					})
				});
			}
			Ext.apply(this, {
				bbar : new Gemma.PagingToolbar({
					pageSize : this.pageSize,
					store : this.store
				})
			});
		} else {
			if (!this.records) {
				Ext.apply(this, {
					store : new Ext.data.Store({
						proxy : new Ext.data.DWRProxy(this.readMethod),
						reader : new Ext.data.ListRangeReader({
							id : "id"
						}, this.record)
					})
				});
			} else {
				Ext.apply(this, {
					store : new Ext.data.Store({
						proxy : new Ext.data.MemoryProxy(this.records),
						reader : new Ext.data.ListRangeReader({}, this.record)
					})
				});
			}
		}

		Ext.apply(this, {
			columns : [{
				id : 'shortName',
				header : "Dataset",
				dataIndex : "shortName",
				renderer : this.formatEE,
		//		width : 80,
				sortable : true
			}, {
				id : 'name',
				header : "Name",
				dataIndex : "name",
			//	width : 120,
				sortable : true
			}, {
				id : 'arrays',
				header : "Arrays",
				dataIndex : "arrayDesignCount",
			//	width : 50,
				sortable : true
			}, {
				id : 'assays',
				header : "Assays",
				dataIndex : "bioAssayCount",
				renderer : this.formatAssayCount,
			//	width : 50,
				sortable : true
			}]
		});

		if (this.rowExpander) {
			Ext.apply(this, {
				rowExpander : new Gemma.EEGridRowExpander({
					tpl : ""
				})
			});
			this.columns.unshift(this.rowExpander);
			Ext.apply(this, {
				plugins : this.rowExpander
			});
		}

		Gemma.ExpressionExperimentGrid.superclass.initComponent.call(this);

		this.on("keypress", function(e) {
			if (e.getCharCode() == Ext.EventObject.DELETE) {
				this.removeSelected();
			}
		}, this);

		this.getStore().on("load", function() {
			this.doLayout();
		}, this);

	},

	afterRender : function() {
		Gemma.ExpressionExperimentGrid.superclass.afterRender.call(this);
		if (this.getTopToolbar()) {
			this.getTopToolbar().grid = this;
		}
	},

	removeSelected : function() {
		var recs = this.getSelectionModel().getSelections();
		for (var x = 0; x < recs.length; x++) { // for r in recs
			// does
			// not
			// work!
			this.getStore().remove(recs[x]);
			this.getView().refresh();
		}
	},

	formatAssayCount : function(value, metadata, record, row, col, ds) {
		return record.get("bioAssayCount");
//		return String
//				.format(
//						"{0} ",
//						record.data.bioAssayCount, record.data.id);
	},

	formatEE : function(value, metadata, record, row, col, ds) {
		var eeTemplate = new Ext.Template("{shortName}");
		return eeTemplate.apply(record.data);
	},

	/**
	 * Return all the ids of the experiments shown in this grid.
	 */
	getEEIds : function() {
		var result = [];
		this.store.each(function(rec) {
			result.push(rec.get("id"));
		});
		return result;
	},

	isEditable : function() {
		return this.editable;
	},

	setEditable : function(b) {
		this.editable = b;
	}

});

Gemma.ExpressionExperimentGrid.updateDatasetInfo = function(datasets, eeMap) {
	for (var i = 0; i < datasets.length; ++i) {
		var ee = eeMap[datasets[i].id];
		if (ee) {
			datasets[i].shortName = ee.shortName;
			datasets[i].name = ee.name;
		}
	}
};

Gemma.EEGridRowExpander = Ext.extend(Ext.grid.RowExpander, {

	fillExpander : function(data, body, rowIndex) {
		Ext.DomHelper.overwrite(body, {
			tag : 'p',
			html : data
		});
	},

	beforeExpand : function(record, body, rowIndex) {
		ExpressionExperimentController.getDescription(record.id, {
			callback : this.fillExpander.createDelegate(this, [body, rowIndex],
					true)
		});
		return true;
	}

});
<|endoftext|>