<commit_before>'use strict';

var Local = require('./Local');
var DataModelJSON = require('../dataModels/JSON');
var features = require('../features');
var aggregations = require('../Shared.js').analytics.util.aggregations;

/**
 * @name behaviors.JSON
 * @desc > Same parameters as {@link behaviors.Behavior#initialize|initialize}, which is called by this constructor.
 * @constructor
 */
var JSON = Local.extend('behaviors.JSON', {

    /**
     * @summary Constructor logic, called _after_{@link Behavior#initialize|Behavior.initialize()}.
     * @desc This method will be called upon instantiation of this class or of any class that extends from this class.
     * > All `initialize()` methods in the inheritance chain are called, in turn, each with the same parameters that were passed to the constructor, beginning with that of the most "senior" class through that of the class of the new instance.
     *
     * @param grid - the hypergrid
     * @param {object[]} dataRows - array of uniform data objects
     * @memberOf behaviors.JSON.prototype
     */
    initialize: function(grid, dataRows) {
        this.setData(dataRows);
    },

    features: [
        features.CellSelection,
        features.KeyPaging,
        features.ColumnPicker,
        features.ColumnResizing,
        features.RowResizing,
        features.Filters,
        features.RowSelection,
        features.ColumnSelection,
        features.ColumnMoving,
        features.ColumnSorting,
        features.CellEditing,
        features.CellClick,
        features.OnHover
    ],

    aggregations: aggregations,

    createColumns: function() {
        var dataModel = this.dataModel;
        var columnCount = dataModel.getColumnCount();
        var headers = dataModel.getHeaders();
        var fields = dataModel.getFields();
        this.clearColumns();
        for (var i = 0; i < columnCount; i++) {
            var header = headers[i];
            var column = this.addColumn(i, header);
            var properties = column.getProperties();
            properties.field = fields[i];
            properties.header = header;
            properties.complexFilter = null;
        }
    },

    getDefaultDataModel: function() {
        return new DataModelJSON(this.grid);
    },

    applyAnalytics: function() {
        this.dataModel.applyAnalytics();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Set the header labels.
     * @param {string[]} headerLabels - The header labels.
     */
    setHeaders: function(headerLabels) {
        this.dataModel.setHeaders(headerLabels);
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @desc * @returns {string[]} The header labels.
     */
    getHeaders: function() {
        return this.dataModel.getHeaders();
    },
    /**
     * @memberOf behaviors.JSON.prototype
     * @return {string} The field at `colIndex`.
     * @param {number} colIndex - the column index of interest
     */
    getField: function(colIndex) {
        return colIndex === -1 ? 'tree' : this.getColumnFromFullList(colIndex).getField();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Set the fields array.
     * @param {string[]} fieldNames - The field names.
     */
    setFields: function(fieldNames) {
        //were defining the columns based on field names....
        //we must rebuild the column definitions
        this.dataModel.setFields(fieldNames);
        this.createColumns();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Get the field names.
     * @returns {string[]}
     */
    getFields: function() {
        return this.dataModel.getFields();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Set the data field.
     * @param {object[]} objects - An array of uniform objects, each being a row in the grid.
     * @param {FilterTree} [filter]
     */
    setData: function(dataRows, filter) {
        this.dataModel.setData(dataRows);
        this.createColumns();

        this.setGlobalFilter(this.getNewFilter());

        var self = this;
        if (this.grid.isColumnAutosizing()) {
            setTimeout(function() {
                self.autosizeAllColumns();
            }, 100);
            self.changed();
        } else {
            setTimeout(function() {
                self.allColumns[-1].checkColumnAutosizing(true);
                self.changed();
            });
        }
    },

    /**
     * @summary Set the top totals.
     * @memberOf behaviors.JSON.prototype
     * @param {Array<Array>} totalRows - array of rows (arrays) of totals
     */
    setTopTotals: function(totalRows) {
        this.dataModel.setTopTotals(totalRows);
    },

    /**
     * @summary Get the top totals.
     * @memberOf behaviors.JSON.prototype
     * @returns {Array<Array>}
     */
    getTopTotals: function() {
        return this.dataModel.getTopTotals();
    },

    /**
     * @summary Set the bottom totals.
     * @memberOf behaviors.JSON.prototype
     * @param {Array<Array>} totalRows - array of rows (arrays) of totals
     */
    setBottomTotals: function(totalRows) {
        this.dataModel.setBottomTotals(totalRows);
    },

    /**
     * @summary Get the bottom totals.
     * @memberOf behaviors.JSON.prototype
     * @returns {Array<Array>}
     */
    getBottomTotals: function() {
        return this.dataModel.getBottomTotals();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Build the fields and headers from the supplied column definitions.
     * ```javascript
     * myJsonBehavior.setColumns([
     *     { title: 'Stock Name', field: 'short_description' },
     *     { title: 'Status', field: 'trading_phase' },
     *     { title: 'Reference Price', field: 'reference_price' }
     * ]);
     * ```
     * @param {Array} columnDefinitions - an array of objects with fields 'title', and 'field'
     */
    setColumns: function(columnDefinitions) {
        this.dataModel.setColumns(columnDefinitions);
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Enhance the double-click event just before it's broadcast to listeners.
     * @param {Point} event
     */
    enhanceDoubleClickEvent: function(event) {
        event.row = this.getRow(event.gridCell.y);
    },

    setDataProvider: function(dataProvider) {
        this.dataModel.setDataProvider(dataProvider);
    },

    hasHierarchyColumn: function() {
        return this.dataModel.hasHierarchyColumn();
    },

    getColumnAlignment: function(x) {
        if (x === 0 && this.hasHierarchyColumn()) {
            return 'left';
        } else {
            return 'center';
        }
    },
    getColumnFromFullList: function(x) {
        return this.allColumns[x];
    },

    getRowSelectionMatrix: function(selectedRows) {
        return this.dataModel.getRowSelectionMatrix(selectedRows);
    },

    getColumnSelectionMatrix: function(selectedColumns) {
        return this.dataModel.getColumnSelectionMatrix(selectedColumns);
    },

    getSelectionMatrix: function(selections) {
        return this.dataModel.getSelectionMatrix(selections);
    },

    getRowSelection: function() {
        var selectedRows = this.getSelectedRows();
        return this.dataModel.getRowSelection(selectedRows);
    },

    getColumnSelection: function() {
        var selectedColumns = this.getSelectedColumns();
        return this.dataModel.getColumnSelection(selectedColumns);
    },

    getSelection: function() {
        var selections = this.getSelections();
        return this.dataModel.getSelection(selections);
    },

    getGroups: function() {
        return this.dataModel.getGroups();
    },
    getAvailableGroups: function() {
        return this.dataModel.getAvailableGroups();
    },
    getHiddenColumns: function() {
        return this.dataModel.getHiddenColumns();
    },
    getVisibleColumns: function() {
        return this.dataModel.getVisibleColumns();
    },

    getSelectedRows: function() {
        var offset = -this.grid.getHeaderRowCount();
        var selections = this.grid.selectionModel.getSelectedRows();
        var result = selections.map(function(each) {
            return each + offset;
        });
        return result;
    },

    getSelectedColumns: function() {
        return this.grid.selectionModel.getSelectedColumns();
    },

    getSelections: function() {
        return this.grid.selectionModel.getSelections();
    },

    getSortedColumnIndexes: function(){
      return this.dataModel.getSortedColumnIndexes();
    },

    sortChanged: function(hiddenColumns){
        var oldSorted = this.getSortedColumnIndexes(),
            dirty = false;
        hiddenColumns = hiddenColumns || this.getHiddenColumns();

        function removeHiddenColumns(){
            oldSorted.forEach(function(i) {
                var j = 0,
                    colIndex;
                while (j < hiddenColumns.length) {
                    colIndex = hiddenColumns[j].index + 1; //hack to get around 0 index
                    if (colIndex === i) {
                        hiddenColumns[j].unSort();
                        dirty = true;
                        break;
                    }
                    j++;
                }
            });
        }
        removeHiddenColumns();
        if (dirty){
            this.applyAnalytics();
            this.grid.repaint();
        }
    }

});

module.exports = JSON;
<commit_msg>Code review refactoring<commit_after>'use strict';

var Local = require('./Local');
var DataModelJSON = require('../dataModels/JSON');
var features = require('../features');
var aggregations = require('../Shared.js').analytics.util.aggregations;

/**
 * @name behaviors.JSON
 * @desc > Same parameters as {@link behaviors.Behavior#initialize|initialize}, which is called by this constructor.
 * @constructor
 */
var JSON = Local.extend('behaviors.JSON', {

    /**
     * @summary Constructor logic, called _after_{@link Behavior#initialize|Behavior.initialize()}.
     * @desc This method will be called upon instantiation of this class or of any class that extends from this class.
     * > All `initialize()` methods in the inheritance chain are called, in turn, each with the same parameters that were passed to the constructor, beginning with that of the most "senior" class through that of the class of the new instance.
     *
     * @param grid - the hypergrid
     * @param {object[]} dataRows - array of uniform data objects
     * @memberOf behaviors.JSON.prototype
     */
    initialize: function(grid, dataRows) {
        this.setData(dataRows);
    },

    features: [
        features.CellSelection,
        features.KeyPaging,
        features.ColumnPicker,
        features.ColumnResizing,
        features.RowResizing,
        features.Filters,
        features.RowSelection,
        features.ColumnSelection,
        features.ColumnMoving,
        features.ColumnSorting,
        features.CellEditing,
        features.CellClick,
        features.OnHover
    ],

    aggregations: aggregations,

    createColumns: function() {
        var dataModel = this.dataModel;
        var columnCount = dataModel.getColumnCount();
        var headers = dataModel.getHeaders();
        var fields = dataModel.getFields();
        this.clearColumns();
        for (var i = 0; i < columnCount; i++) {
            var header = headers[i];
            var column = this.addColumn(i, header);
            var properties = column.getProperties();
            properties.field = fields[i];
            properties.header = header;
            properties.complexFilter = null;
        }
    },

    getDefaultDataModel: function() {
        return new DataModelJSON(this.grid);
    },

    applyAnalytics: function() {
        this.dataModel.applyAnalytics();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Set the header labels.
     * @param {string[]} headerLabels - The header labels.
     */
    setHeaders: function(headerLabels) {
        this.dataModel.setHeaders(headerLabels);
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @desc * @returns {string[]} The header labels.
     */
    getHeaders: function() {
        return this.dataModel.getHeaders();
    },
    /**
     * @memberOf behaviors.JSON.prototype
     * @return {string} The field at `colIndex`.
     * @param {number} colIndex - the column index of interest
     */
    getField: function(colIndex) {
        return colIndex === -1 ? 'tree' : this.getColumnFromFullList(colIndex).getField();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Set the fields array.
     * @param {string[]} fieldNames - The field names.
     */
    setFields: function(fieldNames) {
        //were defining the columns based on field names....
        //we must rebuild the column definitions
        this.dataModel.setFields(fieldNames);
        this.createColumns();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Get the field names.
     * @returns {string[]}
     */
    getFields: function() {
        return this.dataModel.getFields();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Set the data field.
     * @param {object[]} objects - An array of uniform objects, each being a row in the grid.
     * @param {FilterTree} [filter]
     */
    setData: function(dataRows, filter) {
        this.dataModel.setData(dataRows);
        this.createColumns();

        this.setGlobalFilter(this.getNewFilter());

        var self = this;
        if (this.grid.isColumnAutosizing()) {
            setTimeout(function() {
                self.autosizeAllColumns();
            }, 100);
            self.changed();
        } else {
            setTimeout(function() {
                self.allColumns[-1].checkColumnAutosizing(true);
                self.changed();
            });
        }
    },

    /**
     * @summary Set the top totals.
     * @memberOf behaviors.JSON.prototype
     * @param {Array<Array>} totalRows - array of rows (arrays) of totals
     */
    setTopTotals: function(totalRows) {
        this.dataModel.setTopTotals(totalRows);
    },

    /**
     * @summary Get the top totals.
     * @memberOf behaviors.JSON.prototype
     * @returns {Array<Array>}
     */
    getTopTotals: function() {
        return this.dataModel.getTopTotals();
    },

    /**
     * @summary Set the bottom totals.
     * @memberOf behaviors.JSON.prototype
     * @param {Array<Array>} totalRows - array of rows (arrays) of totals
     */
    setBottomTotals: function(totalRows) {
        this.dataModel.setBottomTotals(totalRows);
    },

    /**
     * @summary Get the bottom totals.
     * @memberOf behaviors.JSON.prototype
     * @returns {Array<Array>}
     */
    getBottomTotals: function() {
        return this.dataModel.getBottomTotals();
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Build the fields and headers from the supplied column definitions.
     * ```javascript
     * myJsonBehavior.setColumns([
     *     { title: 'Stock Name', field: 'short_description' },
     *     { title: 'Status', field: 'trading_phase' },
     *     { title: 'Reference Price', field: 'reference_price' }
     * ]);
     * ```
     * @param {Array} columnDefinitions - an array of objects with fields 'title', and 'field'
     */
    setColumns: function(columnDefinitions) {
        this.dataModel.setColumns(columnDefinitions);
    },

    /**
     * @memberOf behaviors.JSON.prototype
     * @description Enhance the double-click event just before it's broadcast to listeners.
     * @param {Point} event
     */
    enhanceDoubleClickEvent: function(event) {
        event.row = this.getRow(event.gridCell.y);
    },

    setDataProvider: function(dataProvider) {
        this.dataModel.setDataProvider(dataProvider);
    },

    hasHierarchyColumn: function() {
        return this.dataModel.hasHierarchyColumn();
    },

    getColumnAlignment: function(x) {
        if (x === 0 && this.hasHierarchyColumn()) {
            return 'left';
        } else {
            return 'center';
        }
    },
    getColumnFromFullList: function(x) {
        return this.allColumns[x];
    },

    getRowSelectionMatrix: function(selectedRows) {
        return this.dataModel.getRowSelectionMatrix(selectedRows);
    },

    getColumnSelectionMatrix: function(selectedColumns) {
        return this.dataModel.getColumnSelectionMatrix(selectedColumns);
    },

    getSelectionMatrix: function(selections) {
        return this.dataModel.getSelectionMatrix(selections);
    },

    getRowSelection: function() {
        var selectedRows = this.getSelectedRows();
        return this.dataModel.getRowSelection(selectedRows);
    },

    getColumnSelection: function() {
        var selectedColumns = this.getSelectedColumns();
        return this.dataModel.getColumnSelection(selectedColumns);
    },

    getSelection: function() {
        var selections = this.getSelections();
        return this.dataModel.getSelection(selections);
    },

    getGroups: function() {
        return this.dataModel.getGroups();
    },
    getAvailableGroups: function() {
        return this.dataModel.getAvailableGroups();
    },
    getHiddenColumns: function() {
        return this.dataModel.getHiddenColumns();
    },
    getVisibleColumns: function() {
        return this.dataModel.getVisibleColumns();
    },

    getSelectedRows: function() {
        var offset = -this.grid.getHeaderRowCount();
        var selections = this.grid.selectionModel.getSelectedRows();
        var result = selections.map(function(each) {
            return each + offset;
        });
        return result;
    },

    getSelectedColumns: function() {
        return this.grid.selectionModel.getSelectedColumns();
    },

    getSelections: function() {
        return this.grid.selectionModel.getSelections();
    },

    getSortedColumnIndexes: function(){
      return this.dataModel.getSortedColumnIndexes();
    },

    sortChanged: function(hiddenColumns){
        var dirty = removeHiddenColumns(
            this.getSortedColumnIndexes(),
            (hiddenColumns || this.getHiddenColumns())
        );
        if (dirty){
            this.applyAnalytics();
            this.grid.repaint();
        }
    }

});

//Logic to moved to adapter layer outside of Hypergrid Core
function removeHiddenColumns(oldSorted, hiddenColumns){
    var dirty = false;
    oldSorted.forEach(function(i) {
        var j = 0,
            colIndex;
        while (j < hiddenColumns.length) {
            colIndex = hiddenColumns[j].index + 1; //hack to get around 0 index
            if (colIndex === i) {
                hiddenColumns[j].unSort();
                dirty = true;
                break;
            }
            j++;
        }
    });
    return dirty;
}

module.exports = JSON;
<|endoftext|>


<commit_before>var P = require("parsimmon");
var ast = require("./ast");

function cons(x, xs) {
    if (arguments.length === 1) {
        return cons.bind(null, x);
    }
    return [x].concat(xs);
}

function wrap(left, middle, right) {
    return P.string(left)
        .then(_)
        .then(middle)
        .skip(_)
        .skip(P.string(right));
}

function spaced(par) {
    return _.then(par).skip(_);
}

function word(str) {
    return P.string(str).skip(_);
}

function spread(f) {
    return function(xs) {
        return f.apply(null, xs);
    };
}

function list0(sep, par) {
    return list1(sep, par).or(P.of([]));
}

function list1(sep, par) {
    var more = sep.then(par).many();
    return par.chain(function(r) {
        return more.map(function(rs) {
            return cons(r, rs);
        });
    });
}

function foldLeft(f) {
    return function(z, xs) {
        return xs.reduce(function(acc, x) {
            return f(acc, x);
        }, z);
    };
}

/// Basic whitespace stuff.

var Newline = P.regex(/\n/);

var NotNewlines = P.regex(/[^\n]*/);

var Comment =
    P.string("#")
    .then(NotNewlines)
    .skip(Newline.or(P.eof));

var _ = P.whitespace.or(Comment).many();

/// High level view of expressions. It starts with TopExpr which eventually
/// delegates down to BinExpr. BinExpr are composed of binary operators with
/// OtherOpExpr on either side. OtherOpExpr are the middle tier of things that
/// don't really look like operators but kinda are, like function application,
/// method calls, etc. Finally, this delegates down to BottomExpr, which is the
/// basic literals of the language, and ParenExpr which goes back up to TopExpr.

var TopExpr = P.lazy(function() {
    return P.alt(
        Block,
        If,
        Let,
        Try,
        Throw,
        Require,
        Error_,
        Match,
        BinExpr
    );
});

var OtherOpExpr = P.lazy(function() {
    return P.alt(
        CallMethod,
        Call,
        GetProperty,
        GetMethod,
        BottomExpr
    );
});

var BottomExpr = P.lazy(function() {
    return P.alt(
        Literal,
        Array_,
        Object_,
        Function_,
        IdentExpr,
        ParenExpr
    );
});

var Expr = TopExpr.or(BottomExpr);

var ParenExpr = wrap("(", Expr, ")");

var Literal = P.lazy("literal", function() {
    return P.alt(
        Number_,
        String_,
        True,
        False,
        Undefined,
        Null
    );
});

/// It's always ok to have whitespace on either side of commas and semicolons.
/// Also, this may eventually have more complicated rules, allowing for newlines
/// instead of visible characters.

var Separator = spaced(P.string(","));
var Terminator = spaced(P.string(";"));

// TODO: Disallow keywords as identifiers.
var Identifier =
    P.regex(/[_a-z][_a-z0-9]*/i)
    .desc("identifier")
    .map(ast.Identifier);

/// I'm not incredibly a fan of having IdentExpr, but it helps the linter know
/// when an identifier is being used for its name vs when it's being used for
/// the value it references.

var IdentExpr =
    Identifier
    .map(ast.IdentifierExpression);

/// Pattern matching section is kinda huge because it mirrors much of the rest
/// of the language, but produces different AST nodes, and is slightly
/// different.

var MatchPattern = P.lazy(function() {
    return P.alt(
        MatchPatternArray,
        MatchPatternObject,
        MatchPatternLiteral,
        MatchPatternSimple,
        MatchPatternParenExpr
    );
});

var MatchPatternSimple =
    Identifier.map(ast.MatchPatternSimple);

var MatchPatternParenExpr = P.lazy(function() {
    return ParenExpr.map(ast.MatchPatternParenExpr);
});

var MatchPatternLiteral = P.lazy(function() {
    return P.alt(
        Number_,
        String_,
        True,
        False,
        Undefined,
        Null
    ).map(ast.MatchPatternLiteral);
});

var MatchPatternObjectPairBasic = P.lazy(function() {
    return P.seq(
        ObjectPairKey.skip(_),
        word(":").then(MatchPattern)
    ).map(spread(ast.MatchPatternObjectPair));
});

var MatchPatternObjectPairShorthand =
    Identifier
    .map(function(i) {
        var str = ast.String(i.data);
        var expr = ast.IdentifierExpression(i);
        return ast.MatchPatternObjectPair(str, expr);
    });

var MatchPatternObjectPair = P.alt(
    MatchPatternObjectPairBasic,
    MatchPatternObjectPairShorthand
);

var MatchPatternObject =
    wrap("{", list0(Separator, MatchPatternObjectPair), "}")
    .map(ast.MatchPatternObject);

var MatchPatternArrayStrict =
    wrap("[", list0(Separator, MatchPattern), "]")
    .map(ast.MatchPatternArray);

var MatchPatternArraySlurpy =
    wrap(
        "[",
        P.seq(
            MatchPattern.skip(Separator).many(),
            word("...").then(MatchPattern)
        ),
        "]"
    ).map(spread(ast.MatchPatternArraySlurpy));

var MatchPatternArray = P.alt(
    MatchPatternArrayStrict,
    MatchPatternArraySlurpy
);

var MatchClause =
    P.seq(
        word("case").then(MatchPattern),
        _.then(word("=>")).then(Expr)
    ).map(spread(ast.MatchClause));

var Match =
    P.seq(
        word("match").then(Expr),
        _.then(MatchClause).atLeast(1).skip(_).skip(P.string("end"))
    ).map(spread(ast.Match));

/// Function calls.

var ArgList =
    wrap("(", list0(Separator, Expr), ")");

var Call =
    P.seq(BottomExpr, _.then(ArgList).atLeast(1))
    .map(spread(foldLeft(ast.Call)));

/// Dot access to properties is really just syntax sugar for using brackets and
/// a string literal, so let's treat it that way in the grammar. The compiler
/// can optimize that part.

var IdentifierAsString =
    Identifier
    .map(function(x) { return x.data; })
    .map(ast.String);

var DotProp =
    word(".")
    .then(IdentifierAsString);

var BracketProp =
    wrap("[", Expr, "]");

var Property = DotProp.or(BracketProp);

/// Property get and method calls.

var GetProperty =
    P.seq(
        BottomExpr,
        _.then(Property).atLeast(1)
    )
    .map(spread(foldLeft(ast.GetProperty)));

var CallMethod =
    P.seq(
        BottomExpr,
        P.seq(
            _.then(Property),
            _.then(ArgList)
        ).atLeast(1)
    ).map(spread(foldLeft(function(acc, x) {
        return ast.CallMethod(acc, x[0], x[1]);
    })));

/// Getting a bound method like `console::log`. Syntax is likely to change on
/// this to match the experimental ES7-style `::console.log` and
/// `::console["log"]`.

var GetMethod =
    P.seq(
        BottomExpr,
        _.then(word("::").then(IdentifierAsString)).atLeast(1)
    ).map(spread(foldLeft(ast.GetMethod)));

/// Function literals.

var Parameter =
    Identifier.map(ast.Parameter);

// Parameters look like this:
// (@this, a, b, c, ...xs)
// The first piece (@this) is the context.
// The second piece (a, b, c) is the positional.
// The third piece (...xs) is the slurpy.
// All pieces are optional.

var ParamSlurpy = word("...").then(Parameter);
var ParamContext = P.string("@").then(Parameter);
var ParamsPositional = list0(Separator, Parameter);

var Params2 =
    P.alt(
        P.seq(ParamsPositional, Separator.then(ParamSlurpy)),
        P.seq(P.of([]), ParamSlurpy),
        P.seq(ParamsPositional, P.of(null)),
        P.of(null)
    );

var Params1 =
    P.alt(
        ParamContext.chain(function(x) {
            return Separator.then(Params2.map(cons(x)));
        }),
        ParamContext.map(function(x) { return [x, [], null]; }),
        Params2.map(cons(null))
    );

var Parameters =
    Params1
    .map(spread(ast.Parameters));

var Function_ =
    P.seq(
        word("fn").then(Identifier.or(P.of(null))),
        _.then(wrap("(", Parameters, ")")),
        _.then(Expr)
    ).map(spread(ast.Function));

/// Code blocks start with "do" to avoid ambiguity with object literals.

var Statement =
    Expr.skip(Terminator);

var Block =
    wrap("do", Statement.atLeast(1), "end")
    .map(ast.Block);

/// If-expression with mandatory else clause.

var If =
    P.seq(
        word("if").then(Expr).skip(_),
        word("then").then(Expr).skip(_),
        word("else").then(Expr)
    ).map(spread(ast.If));

/// This helper is really dense, but basically it process a list of operators
/// and expressions, nesting them in a left-associative manner.

function lbo(ops, e) {
    var aOperators = ops.split(" ");
    var sOperators = aOperators.map(P.string).map(spaced);
    var pOperator = P.alt.apply(null, sOperators).map(ast.Operator);
    var pAll = P.seq(e, P.seq(pOperator, e).many());
    return pAll.map(function(data) {
        return data[1].reduce(function(acc, x) {
            return ast.BinOp(x[0], acc, x[1]);
        }, data[0]);
    });
}

/// Unary operators not (not) and negate (-).

var Not = word("not").then(OtherOpExpr).map(ast.Not);
var Negate = word("-").then(OtherOpExpr).map(ast.Negate);

var UnaryOps = P.alt(Not, Negate, OtherOpExpr);

var b5 = lbo("* /", UnaryOps);
var b4 = lbo("+ -", b5);
var b3 = lbo("++ ~", b4);
var b2 = lbo(">= <= < > == !=", b3);
var b1 = lbo("and or", b2);

var BinExpr = b1;

/// Various single word "unary operators".
/// These all happen before binary operators are parsed.

var Try = word("try").then(Expr).map(ast.Try);
var Throw = word("throw").then(Expr).map(ast.Throw);
var Error_ = word("error").then(Expr).map(ast.Error);
var Require = word("require").then(Expr).map(ast.Require);

/// Variable bindings via "let". Probably going to be changed soon.

var LetBinding =
    P.seq(
        word("let").then(Identifier),
        _.then(word("=")).then(Expr)
    ).map(spread(ast.Binding));

var DefBinding =
    P.seq(
        word("def").then(Identifier).skip(_),
        _.then(wrap("(", Parameters, ")")).skip(_),
        word("=").then(Expr)
    ).map(spread(function(name, params, body) {
        var fn = ast.Function(name, params, body);
        return ast.Binding(name, fn);
    }));

var Binding = LetBinding.or(DefBinding);

var Bindings = list1(_, Binding);

var Let =
    P.seq(
        Bindings,
        _.then(word("in")).then(Expr)
    ).map(spread(ast.Let));

/// Object literal.

var ObjectPairKey = P.lazy(function() {
    return P.alt(
        IdentifierAsString,
        String_,
        ParenExpr
    );
});

var ObjectPairNormal =
    P.seq(
        ObjectPairKey,
        spaced(P.string(":")).then(Expr)
    ).map(spread(ast.Pair));

var ObjectPairShorthand =
    Identifier
    .map(function(i) {
        var str = ast.String(i.data);
        var expr = ast.IdentifierExpression(i);
        return ast.Pair(str, expr);
    });

var ObjectPair = P.alt(
    ObjectPairNormal,
    ObjectPairShorthand
);

var Object_ =
    wrap("{", list0(Separator, ObjectPair), "}")
    .map(ast.Object);

/// Array literal.

var Array_ =
    wrap("[", list0(Separator, Expr), "]")
    .map(ast.Array);

/// Named literals.

var True = P.string("true").result(ast.True());
var False = P.string("false").result(ast.False());
var Null = P.string("null").result(ast.Null());
var Undefined = P.string("undefined").result(ast.Undefined());

/// Numbers.

var Number_ = P.regex(/[0-9]+/)
    .desc("number")
    .map(global.Number)
    .map(ast.Number);

/// Strings.

var DQuote = P.string('"');

var StringChars = P.regex(/[^"\n]*/);

var String_ =
    DQuote.then(StringChars).skip(DQuote)
    .desc("string")
    .map(ast.String);

/// Top level file entry point.

var Script =
    spaced(Expr)
    .map(ast.Script);

var Module =
    spaced(word("export").then(Expr))
    .map(ast.Module)
    .or(Script);

module.exports = {
    Module: Module,
    Expr: Expr,
    Binding: Binding,
    spaced: spaced,
    _: _
};
<commit_msg>Refactors binary operators using .reduce<commit_after>var P = require("parsimmon");
var ast = require("./ast");

function cons(x, xs) {
    if (arguments.length === 1) {
        return cons.bind(null, x);
    }
    return [x].concat(xs);
}

function wrap(left, middle, right) {
    return P.string(left)
        .then(_)
        .then(middle)
        .skip(_)
        .skip(P.string(right));
}

function spaced(par) {
    return _.then(par).skip(_);
}

function word(str) {
    return P.string(str).skip(_);
}

function spread(f) {
    return function(xs) {
        return f.apply(null, xs);
    };
}

function list0(sep, par) {
    return list1(sep, par).or(P.of([]));
}

function list1(sep, par) {
    var more = sep.then(par).many();
    return par.chain(function(r) {
        return more.map(function(rs) {
            return cons(r, rs);
        });
    });
}

function foldLeft(f) {
    return function(z, xs) {
        return xs.reduce(function(acc, x) {
            return f(acc, x);
        }, z);
    };
}

/// Basic whitespace stuff.

var Newline = P.regex(/\n/);

var NotNewlines = P.regex(/[^\n]*/);

var Comment =
    P.string("#")
    .then(NotNewlines)
    .skip(Newline.or(P.eof));

var _ = P.whitespace.or(Comment).many();

/// High level view of expressions. It starts with TopExpr which eventually
/// delegates down to BinExpr. BinExpr are composed of binary operators with
/// OtherOpExpr on either side. OtherOpExpr are the middle tier of things that
/// don't really look like operators but kinda are, like function application,
/// method calls, etc. Finally, this delegates down to BottomExpr, which is the
/// basic literals of the language, and ParenExpr which goes back up to TopExpr.

var TopExpr = P.lazy(function() {
    return P.alt(
        Block,
        If,
        Let,
        Try,
        Throw,
        Require,
        Error_,
        Match,
        BinExpr
    );
});

var OtherOpExpr = P.lazy(function() {
    return P.alt(
        CallMethod,
        Call,
        GetProperty,
        GetMethod,
        BottomExpr
    );
});

var BottomExpr = P.lazy(function() {
    return P.alt(
        Literal,
        Array_,
        Object_,
        Function_,
        IdentExpr,
        ParenExpr
    );
});

var Expr = TopExpr.or(BottomExpr);

var ParenExpr = wrap("(", Expr, ")");

var Literal = P.lazy("literal", function() {
    return P.alt(
        Number_,
        String_,
        True,
        False,
        Undefined,
        Null
    );
});

/// It's always ok to have whitespace on either side of commas and semicolons.
/// Also, this may eventually have more complicated rules, allowing for newlines
/// instead of visible characters.

var Separator = spaced(P.string(","));
var Terminator = spaced(P.string(";"));

// TODO: Disallow keywords as identifiers.
var Identifier =
    P.regex(/[_a-z][_a-z0-9]*/i)
    .desc("identifier")
    .map(ast.Identifier);

/// I'm not incredibly a fan of having IdentExpr, but it helps the linter know
/// when an identifier is being used for its name vs when it's being used for
/// the value it references.

var IdentExpr =
    Identifier
    .map(ast.IdentifierExpression);

/// Pattern matching section is kinda huge because it mirrors much of the rest
/// of the language, but produces different AST nodes, and is slightly
/// different.

var MatchPattern = P.lazy(function() {
    return P.alt(
        MatchPatternArray,
        MatchPatternObject,
        MatchPatternLiteral,
        MatchPatternSimple,
        MatchPatternParenExpr
    );
});

var MatchPatternSimple =
    Identifier.map(ast.MatchPatternSimple);

var MatchPatternParenExpr = P.lazy(function() {
    return ParenExpr.map(ast.MatchPatternParenExpr);
});

var MatchPatternLiteral = P.lazy(function() {
    return P.alt(
        Number_,
        String_,
        True,
        False,
        Undefined,
        Null
    ).map(ast.MatchPatternLiteral);
});

var MatchPatternObjectPairBasic = P.lazy(function() {
    return P.seq(
        ObjectPairKey.skip(_),
        word(":").then(MatchPattern)
    ).map(spread(ast.MatchPatternObjectPair));
});

var MatchPatternObjectPairShorthand =
    Identifier
    .map(function(i) {
        var str = ast.String(i.data);
        var expr = ast.IdentifierExpression(i);
        return ast.MatchPatternObjectPair(str, expr);
    });

var MatchPatternObjectPair = P.alt(
    MatchPatternObjectPairBasic,
    MatchPatternObjectPairShorthand
);

var MatchPatternObject =
    wrap("{", list0(Separator, MatchPatternObjectPair), "}")
    .map(ast.MatchPatternObject);

var MatchPatternArrayStrict =
    wrap("[", list0(Separator, MatchPattern), "]")
    .map(ast.MatchPatternArray);

var MatchPatternArraySlurpy =
    wrap(
        "[",
        P.seq(
            MatchPattern.skip(Separator).many(),
            word("...").then(MatchPattern)
        ),
        "]"
    ).map(spread(ast.MatchPatternArraySlurpy));

var MatchPatternArray = P.alt(
    MatchPatternArrayStrict,
    MatchPatternArraySlurpy
);

var MatchClause =
    P.seq(
        word("case").then(MatchPattern),
        _.then(word("=>")).then(Expr)
    ).map(spread(ast.MatchClause));

var Match =
    P.seq(
        word("match").then(Expr),
        _.then(MatchClause).atLeast(1).skip(_).skip(P.string("end"))
    ).map(spread(ast.Match));

/// Function calls.

var ArgList =
    wrap("(", list0(Separator, Expr), ")");

var Call =
    P.seq(BottomExpr, _.then(ArgList).atLeast(1))
    .map(spread(foldLeft(ast.Call)));

/// Dot access to properties is really just syntax sugar for using brackets and
/// a string literal, so let's treat it that way in the grammar. The compiler
/// can optimize that part.

var IdentifierAsString =
    Identifier
    .map(function(x) { return x.data; })
    .map(ast.String);

var DotProp =
    word(".")
    .then(IdentifierAsString);

var BracketProp =
    wrap("[", Expr, "]");

var Property = DotProp.or(BracketProp);

/// Property get and method calls.

var GetProperty =
    P.seq(
        BottomExpr,
        _.then(Property).atLeast(1)
    )
    .map(spread(foldLeft(ast.GetProperty)));

var CallMethod =
    P.seq(
        BottomExpr,
        P.seq(
            _.then(Property),
            _.then(ArgList)
        ).atLeast(1)
    ).map(spread(foldLeft(function(acc, x) {
        return ast.CallMethod(acc, x[0], x[1]);
    })));

/// Getting a bound method like `console::log`. Syntax is likely to change on
/// this to match the experimental ES7-style `::console.log` and
/// `::console["log"]`.

var GetMethod =
    P.seq(
        BottomExpr,
        _.then(word("::").then(IdentifierAsString)).atLeast(1)
    ).map(spread(foldLeft(ast.GetMethod)));

/// Function literals.

var Parameter =
    Identifier.map(ast.Parameter);

// Parameters look like this:
// (@this, a, b, c, ...xs)
// The first piece (@this) is the context.
// The second piece (a, b, c) is the positional.
// The third piece (...xs) is the slurpy.
// All pieces are optional.

var ParamSlurpy = word("...").then(Parameter);
var ParamContext = P.string("@").then(Parameter);
var ParamsPositional = list0(Separator, Parameter);

var Params2 =
    P.alt(
        P.seq(ParamsPositional, Separator.then(ParamSlurpy)),
        P.seq(P.of([]), ParamSlurpy),
        P.seq(ParamsPositional, P.of(null)),
        P.of(null)
    );

var Params1 =
    P.alt(
        ParamContext.chain(function(x) {
            return Separator.then(Params2.map(cons(x)));
        }),
        ParamContext.map(function(x) { return [x, [], null]; }),
        Params2.map(cons(null))
    );

var Parameters =
    Params1
    .map(spread(ast.Parameters));

var Function_ =
    P.seq(
        word("fn").then(Identifier.or(P.of(null))),
        _.then(wrap("(", Parameters, ")")),
        _.then(Expr)
    ).map(spread(ast.Function));

/// Code blocks start with "do" to avoid ambiguity with object literals.

var Statement =
    Expr.skip(Terminator);

var Block =
    wrap("do", Statement.atLeast(1), "end")
    .map(ast.Block);

/// If-expression with mandatory else clause.

var If =
    P.seq(
        word("if").then(Expr).skip(_),
        word("then").then(Expr).skip(_),
        word("else").then(Expr)
    ).map(spread(ast.If));

/// Unary operators not (not) and negate (-).

var Not = word("not").then(OtherOpExpr).map(ast.Not);
var Negate = word("-").then(OtherOpExpr).map(ast.Negate);

var UnaryOps = P.alt(Not, Negate, OtherOpExpr);

var x = y;
var y = x;

var binOps = [
    "* /",
    "+ -",
    "++ ~",
    ">= <= < > == !=",
    "and or",
];

/// This helper is really dense, but basically it process a list of operators
/// and expressions, nesting them in a left-associative manner.

function leftBinOp(e, ops) {
    var aOperators = ops.split(" ");
    var sOperators = aOperators.map(P.string).map(spaced);
    var pOperator = P.alt.apply(null, sOperators).map(ast.Operator);
    var pAll = P.seq(e, P.seq(pOperator, e).many());
    return pAll.map(function(data) {
        return data[1].reduce(function(acc, x) {
            return ast.BinOp(x[0], acc, x[1]);
        }, data[0]);
    });
}

// NOTE: This only works because all operators are left-associative. If there is
// ever a right-associative operator in here, `reduce` will need to use a
// different function.
var BinExpr = binOps.reduce(leftBinOp, UnaryOps);

/// Various single word "unary operators".
/// These all happen before binary operators are parsed.

var Try = word("try").then(Expr).map(ast.Try);
var Throw = word("throw").then(Expr).map(ast.Throw);
var Error_ = word("error").then(Expr).map(ast.Error);
var Require = word("require").then(Expr).map(ast.Require);

/// Variable bindings via "let". Probably going to be changed soon.

var LetBinding =
    P.seq(
        word("let").then(Identifier),
        _.then(word("=")).then(Expr)
    ).map(spread(ast.Binding));

var DefBinding =
    P.seq(
        word("def").then(Identifier).skip(_),
        _.then(wrap("(", Parameters, ")")).skip(_),
        word("=").then(Expr)
    ).map(spread(function(name, params, body) {
        var fn = ast.Function(name, params, body);
        return ast.Binding(name, fn);
    }));

var Binding = LetBinding.or(DefBinding);

var Bindings = list1(_, Binding);

var Let =
    P.seq(
        Bindings,
        _.then(word("in")).then(Expr)
    ).map(spread(ast.Let));

/// Object literal.

var ObjectPairKey = P.lazy(function() {
    return P.alt(
        IdentifierAsString,
        String_,
        ParenExpr
    );
});

var ObjectPairNormal =
    P.seq(
        ObjectPairKey,
        spaced(P.string(":")).then(Expr)
    ).map(spread(ast.Pair));

var ObjectPairShorthand =
    Identifier
    .map(function(i) {
        var str = ast.String(i.data);
        var expr = ast.IdentifierExpression(i);
        return ast.Pair(str, expr);
    });

var ObjectPair = P.alt(
    ObjectPairNormal,
    ObjectPairShorthand
);

var Object_ =
    wrap("{", list0(Separator, ObjectPair), "}")
    .map(ast.Object);

/// Array literal.

var Array_ =
    wrap("[", list0(Separator, Expr), "]")
    .map(ast.Array);

/// Named literals.

var True = P.string("true").result(ast.True());
var False = P.string("false").result(ast.False());
var Null = P.string("null").result(ast.Null());
var Undefined = P.string("undefined").result(ast.Undefined());

/// Numbers.

var Number_ = P.regex(/[0-9]+/)
    .desc("number")
    .map(global.Number)
    .map(ast.Number);

/// Strings.

var DQuote = P.string('"');

var StringChars = P.regex(/[^"\n]*/);

var String_ =
    DQuote.then(StringChars).skip(DQuote)
    .desc("string")
    .map(ast.String);

/// Top level file entry point.

var Script =
    spaced(Expr)
    .map(ast.Script);

var Module =
    spaced(word("export").then(Expr))
    .map(ast.Module)
    .or(Script);

module.exports = {
    Module: Module,
    Expr: Expr,
    Binding: Binding,
    spaced: spaced,
    _: _
};
<|endoftext|>


<commit_before>var availableCommands    = {};
var keyToCommandRegistry = {};

function addCommand(command, description, isBackgroundCommand, passCountToFunction) {
  if (availableCommands[command])
  {
    console.log(command, "is already defined! Check commands.js for duplicates.");
    return;
  }

  availableCommands[command] = { description: description,
                                 isBackgroundCommand: isBackgroundCommand,
                                 passCountToFunction: passCountToFunction
                               };
}

function mapKeyToCommand(key, command) {
  if (!availableCommands[command])
  {
    console.log(command, "doesn't exist!");
    return;
  }

  keyToCommandRegistry[key] = { command: command,
                                isBackgroundCommand: availableCommands[command].isBackgroundCommand,
                                passCountToFunction: availableCommands[command].passCountToFunction
                              };
}

function unmapKey(key) { delete keyToCommandRegistry[key]; }

/* Lower-case the appropriate portions of named keys.
 *
 * A key name is one of three forms exemplified by <c-a> <left> or <c-f12>
 * (prefixed normal key, named key, or prefixed named key). Internally, for
 * simplicity, we would like prefixes and key names to be lowercase, though
 * humans may prefer other forms <Left> or <C-a>.
 * On the other hand, <c-a> and <c-A> are different named keys - for one of
 * them you have to press "shift" as well.
 */
function normalizeKey(key) {
    return key.replace(/<[acm]-/ig, function(match){ return match.toLowerCase(); })
              .replace(/<([acm]-)?([a-zA-Z0-9]{2,5})>/g, function(match, optionalPrefix, keyName){
                  return "<" + ( optionalPrefix ? optionalPrefix : "") + keyName.toLowerCase() + ">";
              });
}

function parseCustomKeyMappings(customKeyMappings) {
  lines = customKeyMappings.split("\n");

  for (var i = 0; i < lines.length; i++) {
    if (lines[i][0] == "\"" || lines[i][0] == "#") { continue }
    split_line = lines[i].split(/\s+/);

    var lineCommand = split_line[0];

    if (lineCommand == "map") {
      if (split_line.length != 3) { continue; }
      var key = normalizeKey(split_line[1]);
      var vimiumCommand = split_line[2];

      if (!availableCommands[vimiumCommand]) { continue }

      console.log("Mapping", key, "to", vimiumCommand);
      mapKeyToCommand(key, vimiumCommand);
    }
    else if (lineCommand == "unmap") {
      if (split_line.length != 2) { continue; }

      var key = normalizeKey(split_line[1]);

      console.log("Unmapping", key);
      unmapKey(key);
    }
    else if (lineCommand == "unmapAll") {
      keyToCommandRegistry = {};
    }
  }
}

function clearKeyMappingsAndSetDefaults() {
  keyToCommandRegistry = {};

  mapKeyToCommand("?", "showHelp");
  mapKeyToCommand("j", "scrollDown");
  mapKeyToCommand("k", "scrollUp");
  mapKeyToCommand("h", "scrollLeft");
  mapKeyToCommand("l", "scrollRight");
  mapKeyToCommand("gg", "scrollToTop");
  mapKeyToCommand("G", "scrollToBottom");
  mapKeyToCommand("zH", "scrollToLeft");
  mapKeyToCommand("zL", "scrollToRight");
  mapKeyToCommand("<c-e>", "scrollDown");
  mapKeyToCommand("<c-y>", "scrollUp");
  mapKeyToCommand("<c-d>", "scrollPageDown");
  mapKeyToCommand("<c-u>", "scrollPageUp");
  mapKeyToCommand("<c-f>", "scrollFullPageDown");
  mapKeyToCommand("<c-b>", "scrollFullPageUp");
  mapKeyToCommand("r", "reload");
  mapKeyToCommand("gs", "toggleViewSource");

  mapKeyToCommand("i", "enterInsertMode");

  mapKeyToCommand("H", "goBack");
  mapKeyToCommand("L", "goForward");
  mapKeyToCommand("gu", "goUp");

  mapKeyToCommand("zi", "zoomIn");
  mapKeyToCommand("zo", "zoomOut");
  mapKeyToCommand("z0", "zoomReset");

  mapKeyToCommand("gi", "focusInput");

  mapKeyToCommand("f",     "activateLinkHintsMode");
  mapKeyToCommand("F",     "activateLinkHintsModeToOpenInNewTab");
  mapKeyToCommand("<a-f>", "activateLinkHintsModeWithQueue");

  mapKeyToCommand("/", "enterFindMode");
  mapKeyToCommand("n", "performFind");
  mapKeyToCommand("N", "performBackwardsFind");

  mapKeyToCommand("[[", "goPrevious");
  mapKeyToCommand("]]", "goNext");

  mapKeyToCommand("yy", "copyCurrentUrl");

  mapKeyToCommand("K", "nextTab");
  mapKeyToCommand("J", "previousTab");
  mapKeyToCommand("gt", "nextTab");
  mapKeyToCommand("gT", "previousTab");

  mapKeyToCommand("t", "createTab");
  mapKeyToCommand("d", "removeTab");
  mapKeyToCommand("u", "restoreTab");

  mapKeyToCommand("gf", "nextFrame");
}

// Navigating the current page:
addCommand("showHelp",            "Show help",  true);
addCommand("scrollDown",          "Scroll down");
addCommand("scrollUp",            "Scroll up");
addCommand("scrollLeft",          "Scroll left");
addCommand("scrollRight",         "Scroll right");
addCommand("scrollToTop",         "Scroll to the top of the page");
addCommand("scrollToBottom",      "Scroll to the bottom of the page");
addCommand("scrollToLeft",        "Scroll to the left of the page");
addCommand("scrollToRight",       "Scroll to the right of the page");
addCommand("scrollPageDown",      "Scroll a page down");
addCommand("scrollPageUp",        "Scroll a page up");
addCommand("scrollFullPageDown",  "Scroll a full page down");
addCommand("scrollFullPageUp",    "Scroll a full page up");

addCommand("reload",              "Reload the page");
addCommand("toggleViewSource",    "View page source");
addCommand("zoomIn",              "Zoom in");
addCommand("zoomOut",             "Zoom out");
addCommand("zoomReset",             "Reset zoom to default value");
addCommand("copyCurrentUrl",      "Copy the current URL to the clipboard");

addCommand("enterInsertMode",     "Enter insert mode");

addCommand("focusInput",          "Focus the first (or n-th) text box on the page", false, true);

addCommand("activateLinkHintsMode",               "Enter link hints mode to open links in current tab");
addCommand("activateLinkHintsModeToOpenInNewTab", "Enter link hints mode to open links in new tab");
addCommand("activateLinkHintsModeWithQueue",      "Enter link hints mode to open multiple links in a new tab");

addCommand("enterFindMode",        "Enter find mode");
addCommand("performFind",          "Cycle forward to the next find match");
addCommand("performBackwardsFind", "Cycle backward to the previous find match");

addCommand("goPrevious",          "Follow the link labeled previous or <");
addCommand("goNext",              "Follow the link labeled next or >");

// Navigating your history:
addCommand("goBack",              "Go back in history");
addCommand("goForward",           "Go forward in history");

// Navigating the URL hierarchy
addCommand("goUp",                "Go up the URL hierarchy", false, true);

// Manipulating tabs:
addCommand("nextTab",             "Go one tab right",  true);
addCommand("previousTab",         "Go one tab left",   true);
addCommand("createTab",           "Create new tab",    true);
addCommand("removeTab",           "Close current tab", true);
addCommand("restoreTab",          "Restore closed tab", true);

addCommand("nextFrame",           "Cycle forward to the next frame on the page", true);

// An ordered listing of all available commands, grouped by type. This is the order they will
// be shown in the help page.
var commandGroups = {
  pageNavigation:
    ["scrollDown", "scrollUp", "scrollLeft", "scrollRight",
     "scrollToTop", "scrollToBottom", "scrollToLeft", "scrollToRight", "scrollPageDown",
     "scrollPageUp", "scrollFullPageUp", "scrollFullPageDown",
     "reload", "toggleViewSource", "zoomIn", "zoomOut", "zoomReset", "copyCurrentUrl", "goUp",
     "enterInsertMode", "focusInput",
     "activateLinkHintsMode", "activateLinkHintsModeToOpenInNewTab", "activateLinkHintsModeWithQueue",
     "goPrevious", "goNext",
     "enterFindMode", "performFind", "performBackwardsFind", "nextFrame"],
  historyNavigation:
    ["goBack", "goForward"],
  tabManipulation:
    ["nextTab", "previousTab", "createTab", "removeTab", "restoreTab"],
  misc:
    ["showHelp"]
};
<commit_msg>Remove some boilerplate code when restoring default mappings<commit_after>var availableCommands    = {};
var keyToCommandRegistry = {};

function addCommand(command, description, isBackgroundCommand, passCountToFunction) {
  if (availableCommands[command])
  {
    console.log(command, "is already defined! Check commands.js for duplicates.");
    return;
  }

  availableCommands[command] = { description: description,
                                 isBackgroundCommand: isBackgroundCommand,
                                 passCountToFunction: passCountToFunction
                               };
}

function mapKeyToCommand(key, command) {
  if (!availableCommands[command])
  {
    console.log(command, "doesn't exist!");
    return;
  }

  keyToCommandRegistry[key] = { command: command,
                                isBackgroundCommand: availableCommands[command].isBackgroundCommand,
                                passCountToFunction: availableCommands[command].passCountToFunction
                              };
}

function unmapKey(key) { delete keyToCommandRegistry[key]; }

/* Lower-case the appropriate portions of named keys.
 *
 * A key name is one of three forms exemplified by <c-a> <left> or <c-f12>
 * (prefixed normal key, named key, or prefixed named key). Internally, for
 * simplicity, we would like prefixes and key names to be lowercase, though
 * humans may prefer other forms <Left> or <C-a>.
 * On the other hand, <c-a> and <c-A> are different named keys - for one of
 * them you have to press "shift" as well.
 */
function normalizeKey(key) {
    return key.replace(/<[acm]-/ig, function(match){ return match.toLowerCase(); })
              .replace(/<([acm]-)?([a-zA-Z0-9]{2,5})>/g, function(match, optionalPrefix, keyName){
                  return "<" + ( optionalPrefix ? optionalPrefix : "") + keyName.toLowerCase() + ">";
              });
}

function parseCustomKeyMappings(customKeyMappings) {
  lines = customKeyMappings.split("\n");

  for (var i = 0; i < lines.length; i++) {
    if (lines[i][0] == "\"" || lines[i][0] == "#") { continue }
    split_line = lines[i].split(/\s+/);

    var lineCommand = split_line[0];

    if (lineCommand == "map") {
      if (split_line.length != 3) { continue; }
      var key = normalizeKey(split_line[1]);
      var vimiumCommand = split_line[2];

      if (!availableCommands[vimiumCommand]) { continue }

      console.log("Mapping", key, "to", vimiumCommand);
      mapKeyToCommand(key, vimiumCommand);
    }
    else if (lineCommand == "unmap") {
      if (split_line.length != 2) { continue; }

      var key = normalizeKey(split_line[1]);

      console.log("Unmapping", key);
      unmapKey(key);
    }
    else if (lineCommand == "unmapAll") {
      keyToCommandRegistry = {};
    }
  }
}

function clearKeyMappingsAndSetDefaults() {
  keyToCommandRegistry = {};

  var defaultKeyMappings = {
    "?": "showHelp",
    "j": "scrollDown",
    "k": "scrollUp",
    "h": "scrollLeft",
    "l": "scrollRight",
    "gg": "scrollToTop",
    "G": "scrollToBottom",
    "zH": "scrollToLeft",
    "zL": "scrollToRight",
    "<c-e>": "scrollDown",
    "<c-y>": "scrollUp",
    "<c-d>": "scrollPageDown",
    "<c-u>": "scrollPageUp",
    "<c-f>": "scrollFullPageDown",
    "<c-b>": "scrollFullPageUp",
    "r": "reload",
    "gs": "toggleViewSource",

    "i": "enterInsertMode",

    "H": "goBack",
    "L": "goForward",
    "gu": "goUp",

    "zi": "zoomIn",
    "zo": "zoomOut",
    "z0": "zoomReset",

    "gi": "focusInput",

    "f":     "activateLinkHintsMode",
    "F":     "activateLinkHintsModeToOpenInNewTab",
    "<a-f>": "activateLinkHintsModeWithQueue",

    "/": "enterFindMode",
    "n": "performFind",
    "N": "performBackwardsFind",

    "[[": "goPrevious",
    "]]": "goNext",

    "yy": "copyCurrentUrl",

    "K": "nextTab",
    "J": "previousTab",
    "gt": "nextTab",
    "gT": "previousTab",

    "t": "createTab",
    "d": "removeTab",
    "u": "restoreTab",

    "gf": "nextFrame"
  };

  for (var key in defaultKeyMappings)
    mapKeyToCommand(key, defaultKeyMappings[key]);
}

// Navigating the current page:
addCommand("showHelp",            "Show help",  true);
addCommand("scrollDown",          "Scroll down");
addCommand("scrollUp",            "Scroll up");
addCommand("scrollLeft",          "Scroll left");
addCommand("scrollRight",         "Scroll right");
addCommand("scrollToTop",         "Scroll to the top of the page");
addCommand("scrollToBottom",      "Scroll to the bottom of the page");
addCommand("scrollToLeft",        "Scroll to the left of the page");
addCommand("scrollToRight",       "Scroll to the right of the page");
addCommand("scrollPageDown",      "Scroll a page down");
addCommand("scrollPageUp",        "Scroll a page up");
addCommand("scrollFullPageDown",  "Scroll a full page down");
addCommand("scrollFullPageUp",    "Scroll a full page up");

addCommand("reload",              "Reload the page");
addCommand("toggleViewSource",    "View page source");
addCommand("zoomIn",              "Zoom in");
addCommand("zoomOut",             "Zoom out");
addCommand("zoomReset",             "Reset zoom to default value");
addCommand("copyCurrentUrl",      "Copy the current URL to the clipboard");

addCommand("enterInsertMode",     "Enter insert mode");

addCommand("focusInput",          "Focus the first (or n-th) text box on the page", false, true);

addCommand("activateLinkHintsMode",               "Enter link hints mode to open links in current tab");
addCommand("activateLinkHintsModeToOpenInNewTab", "Enter link hints mode to open links in new tab");
addCommand("activateLinkHintsModeWithQueue",      "Enter link hints mode to open multiple links in a new tab");

addCommand("enterFindMode",        "Enter find mode");
addCommand("performFind",          "Cycle forward to the next find match");
addCommand("performBackwardsFind", "Cycle backward to the previous find match");

addCommand("goPrevious",          "Follow the link labeled previous or <");
addCommand("goNext",              "Follow the link labeled next or >");

// Navigating your history:
addCommand("goBack",              "Go back in history");
addCommand("goForward",           "Go forward in history");

// Navigating the URL hierarchy
addCommand("goUp",                "Go up the URL hierarchy", false, true);

// Manipulating tabs:
addCommand("nextTab",             "Go one tab right",  true);
addCommand("previousTab",         "Go one tab left",   true);
addCommand("createTab",           "Create new tab",    true);
addCommand("removeTab",           "Close current tab", true);
addCommand("restoreTab",          "Restore closed tab", true);

addCommand("nextFrame",           "Cycle forward to the next frame on the page", true);

// An ordered listing of all available commands, grouped by type. This is the order they will
// be shown in the help page.
var commandGroups = {
  pageNavigation:
    ["scrollDown", "scrollUp", "scrollLeft", "scrollRight",
     "scrollToTop", "scrollToBottom", "scrollToLeft", "scrollToRight", "scrollPageDown",
     "scrollPageUp", "scrollFullPageUp", "scrollFullPageDown",
     "reload", "toggleViewSource", "zoomIn", "zoomOut", "zoomReset", "copyCurrentUrl", "goUp",
     "enterInsertMode", "focusInput",
     "activateLinkHintsMode", "activateLinkHintsModeToOpenInNewTab", "activateLinkHintsModeWithQueue",
     "goPrevious", "goNext",
     "enterFindMode", "performFind", "performBackwardsFind", "nextFrame"],
  historyNavigation:
    ["goBack", "goForward"],
  tabManipulation:
    ["nextTab", "previousTab", "createTab", "removeTab", "restoreTab"],
  misc:
    ["showHelp"]
};
<|endoftext|>


<commit_before>var _ = require('./utils.js');

// Breadth-first walk the tree in reverse order
var inverseBreadth = function(node, cb) {
  if(!node.children) return;
  for(var i = node.children.length - 1; i >= 0; i--) {
    cb(node.children[i]);
  };
  _.each(node.children, function(child) {
    inverseBreadth(child, cb);
  });
};

// Clean tree from alternatives and temporary data
var cleanTree = function(node) {
  if(node.children) {
    for(var i = node.children.length - 1; i >= 0; i--) {
      var child = node.children[i];
      if(child.type == 'Alternative') {
        node.children.splice(i, 1);
      } else {
        cleanTree(child);
      }
    }
  }
  if(node.slurpy) delete node.slurpy;
  return node;
};

var Parser = {
  indentSpaces: 2,

  // Parse the given template source and spit out a traversable tree
  parseTemplate: function(tmpl) {
    var self = this;
    var lines = tmpl.split('\n');
    var lastIndent = 0;
    var lineNum = 0;
    var top = {
      type: 'TOP',
      children: []
    };
    var lastNode = top;
    var stack = [top];
    var slurpyMode = false;
    _.each(lines, function(line) {
      lineNum++;
      var indent = Math.max(0, line.search(/\S/)) / self.indentSpaces;
      // Regard anything below a slurpy tag as text
      if(slurpyMode && (indent > lastIndent || !line.replace(/\s/g, '').length)) {
        var parent = lastNode;
        parent.children.push({
          type: 'Text',
          content: line.slice(lastIndent * self.indentSpaces + self.indentSpaces) + '\n'
        });
        return;
      }
      slurpyMode = false;
      // Remove indentation
      line = line.slice(indent * self.indentSpaces);
      // Ignore comments
      var ci = line.indexOf('//');
      if(ci == 0) return;
      // Ignore empty lines
      if(!line.replace(/\s/g, '').length) return;
      // Push and pop the current parent from the stack
      // as dictated by indentation
      if(indent > lastIndent) {
        if(indent - lastIndent > 1) {
          throw({message: 'Too much indentation', lineNum: lineNum});
        }
        if(lastNode.content) throw({message: 'Elements cannot have both text and children elements', lineNum: lineNum});
        stack.push(lastNode);
      } else if(indent < lastIndent) {
        _.times(lastIndent - indent, function() {
          stack.pop();
        });
      }
      var node = self.parseLine(line);
      // Add the generated node to its parent
      var parent = _.last(stack);
      parent.children.push(node);
      // If we have a multiTags hierarchy -> use rightmost child as lastNode
      var child = node;
      while(child.children && child.children[0]) {
        child = child.children[0];
      };
      lastIndent = indent;
      lastNode = child;
      if(node.slurpy) {
        slurpyMode = true;
      }
    });
    // Squash alternatives into their respective statements
    var previous;
    var alternativeStack = [];
    inverseBreadth(top, function(node) {
      // Collect alternatives from bottom to top
      if(node.type == 'Alternative') {
        alternativeStack.push(node);
      // Attach to next statement we encounter
      } else {
        if(alternativeStack.length) {
          node.alternatives = _.map(alternativeStack.reverse(), function(alt) {
            return alt.children;
          });
          alternativeStack = [];
        }
      }
      previous = node;
    });
    // Remove alternatives
    return cleanTree(top);
  },

  // Convert line into tree node
  parseLine: function(line) {
    // Statement
    if(line.indexOf('{{') == 0) {
      return this.parseStatement(line);
    // Text
    } else if(line.indexOf('|') == 0) {
      return {
        type: 'Text',
        content: line.slice(2) + '\n'
      };
    // Tag definition
    } else {
      return this.parseTag(line);
    }
  },

  // Parse all major components from a tag, including inline content
  parseTag: function(line) {
    var self = this;
    var m = line.match(/(([\w-#\.]+\s*>\s*)*)([\w-]+)?(#([\w-]+))?((\.[\w-]+)*)(\((.*)\))?(\.)?( (.*))?/);
    var multiTags = m[1];
    var tag = m[3] || 'div';
    var id = m[5];
    var classes = m[6] ? m[6].slice(1).split('.') : [];
    var inParens = m[9];
    var dot = m[10];
    var content = m[12];
    var attributes = {};
    var statements = [];
    // Parse attributes
    if(inParens) {
      var attrDefinitions;
      var i = inParens.indexOf('{{');
      if(i > -1) {
        attrDefinitions = inParens.slice(0, i);
        var statementDefinitions = inParens.slice(i, inParens.length);
        statements = self.parseMicroStatements(statementDefinitions);
      } else {
        attrDefinitions = inParens;
      }
      _.each(_.scan(attrDefinitions, /([\w-]+?)=(['"])(.+?)\2\s?/g), function(m) { // '
        var key = m[1];
        var value = m[3];
        attributes[key] = value;
      });
      // _.each(_.scan(statementDefinitions, /{{(\w+)\s+(\w+)\s+(\w+)}}/g), function(m) {
      //   statements[m[2]] = m[3];
      // });
    }
    // Make AST node
    var tag = {
      type: 'HTMLTag',
      tag: tag,
      id: id,
      classes: classes,
      attributes: attributes,
      content: content,
      statements: statements,
      slurpy: !!dot,
      children: []
    };
    // Build a hierarchy from multi tags
    if(multiTags) {
      // Remove trailing '>' before splitting
      multiTags = multiTags.trim().slice(0, -1);
      var tags = _.map(multiTags.split('>'), function(t) {
        return self.parseTag(t.trim());
      });
      var top = tags.shift();
      var prev = top;
      _.each(tags, function(t) {
        prev.children.push(t);
        prev = t;
      });
      prev.children.push(tag);
      return top;
    } else {
      return tag;
    }
  },

  parseMicroStatements: function(string) {
    var statements = [];
    _.each(_.scan(string, /{{(\w+)\s+(.*)}}/g), function(m) {
      var statement = m[1];
      var rest = m[2];
      if(statement == 'as') {
        statements.push({statement: 'as', varName: rest});
      } else if(statement == 'action') {
        var m = rest.match(/(\w+)\s+(\w+)/);
        statements.push({statement: 'action', event: m[1], method: m[2]});
      } else {
        throw('Unknown statement: ' + statement);
      }
    });
    return statements;
  },

  // Takes an statement line and creates the approriate node
  parseStatement: function(line) {
    var m;
    var out;
    // if-greater
    if(m = line.match(/{{if\s+(.+)\s+>\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-greater',
        path1: m[1],
        path2: m[2]
      };
    // if-equal
    } else if(m = line.match(/{{if\s+(.+)\s+==\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-equal',
        path1: m[1],
        path2: m[2]
      };
    // if-not-equal
    } else if(m = line.match(/{{if\s+(.+)\s+!=\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-not-equal',
        path1: m[1],
        path2: m[2]
      };
    // if
    } else if(m = line.match(/{{if\s+(.+)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if',
        path: m[1]
      };
    // for
    } else if(m = line.match(/{{for\s+((\w+)\s+in\s+)?(.+)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'for',
        itemPath: m[2],
        itemsPath: m[3]
      };
    // view
    } else if(m = line.match(/{{view\s+(\w+)?(\(.*\))?}}/)) {
      var parens = m[2];
      var args = parens ? _.map(parens.slice(1, -1).split(','), function(argument) {
        return argument.replace(/\s/g, '');
      }) : [];
      out = {
        type: 'Statement',
        keyword: 'view',
        viewModel: m[1],
        arguments: args
      };
    // import
    } else if(m = line.match(/{{import\s+(\w+)(\(.*\))?}}/)) {
      var parens = m[2];
      var args = {};
      if(parens) {
        _.each(parens.slice(1, -1).split(','), function(argument) {
          var pair = argument.replace(/\s/g, '').split(':');
          var vari = pair[0];
          var expr = pair[1];
          args[vari] = expr;
        });
      }
      out = {
        type: 'Statement',
        keyword: 'import',
        templateName: m[1] + '.tmpl',
        arguments: args
      };
    // content
    } else if(m = line.match(/{{content}}/)) {
      out = {
        type: 'Statement',
        keyword: 'content'
      };
    // client
    } else if(m = line.match(/{{client}}/)) {
      out = {
        type: 'Statement',
        keyword: 'client'
      };
    // route
    } else if(m = line.match(/{{route (.*)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'route',
        path: m[1]
      };
    // =>
    } else if(m = line.match(/{{=>}}/)) {
      out = {
        type: 'Alternative'
      };
    } else {
      throw('Unknown statement: ' + line);
    }
    out.children = [];
    return out;
  }
};

module.exports = Parser;
<commit_msg>Shrink template bundle even further<commit_after>var _ = require('./utils.js');

// Breadth-first walk the tree in reverse order
var inverseBreadth = function(node, cb) {
  if(!node.children) return;
  for(var i = node.children.length - 1; i >= 0; i--) {
    cb(node.children[i]);
  };
  _.each(node.children, function(child) {
    inverseBreadth(child, cb);
  });
};

// Clean tree from alternatives and temporary data
var cleanTree = function(node) {
  if(node.children) {
    for(var i = node.children.length - 1; i >= 0; i--) {
      var child = node.children[i];
      if(child.type == 'Alternative') {
        node.children.splice(i, 1);
      } else {
        cleanTree(child);
      }
    }
  }
  delete node.slurpy;
  if(node.attributes && !_.keys(node.attributes).length) delete node.attributes;
  if(node.statements && !node.statements.length) delete node.statements;
  if(node.children && !node.children.length) delete node.children;
  if(node.classes && !node.classes.length) delete node.classes;
  return node;
};

var Parser = {
  indentSpaces: 2,

  // Parse the given template source and spit out a traversable tree
  parseTemplate: function(tmpl) {
    var self = this;
    var lines = tmpl.split('\n');
    var lastIndent = 0;
    var lineNum = 0;
    var top = {
      type: 'TOP',
      children: []
    };
    var lastNode = top;
    var stack = [top];
    var slurpyMode = false;
    _.each(lines, function(line) {
      lineNum++;
      var indent = Math.max(0, line.search(/\S/)) / self.indentSpaces;
      // Regard anything below a slurpy tag as text
      if(slurpyMode && (indent > lastIndent || !line.replace(/\s/g, '').length)) {
        var parent = lastNode;
        parent.children.push({
          type: 'Text',
          content: line.slice(lastIndent * self.indentSpaces + self.indentSpaces) + '\n'
        });
        return;
      }
      slurpyMode = false;
      // Remove indentation
      line = line.slice(indent * self.indentSpaces);
      // Ignore comments
      var ci = line.indexOf('//');
      if(ci == 0) return;
      // Ignore empty lines
      if(!line.replace(/\s/g, '').length) return;
      // Push and pop the current parent from the stack
      // as dictated by indentation
      if(indent > lastIndent) {
        if(indent - lastIndent > 1) {
          throw({message: 'Too much indentation', lineNum: lineNum});
        }
        if(lastNode.content) throw({message: 'Elements cannot have both text and children elements', lineNum: lineNum});
        stack.push(lastNode);
      } else if(indent < lastIndent) {
        _.times(lastIndent - indent, function() {
          stack.pop();
        });
      }
      var node = self.parseLine(line);
      // Add the generated node to its parent
      var parent = _.last(stack);
      parent.children.push(node);
      // If we have a multiTags hierarchy -> use rightmost child as lastNode
      var child = node;
      while(child.children && child.children[0]) {
        child = child.children[0];
      };
      lastIndent = indent;
      lastNode = child;
      if(node.slurpy) {
        slurpyMode = true;
      }
    });
    // Squash alternatives into their respective statements
    var previous;
    var alternativeStack = [];
    inverseBreadth(top, function(node) {
      // Collect alternatives from bottom to top
      if(node.type == 'Alternative') {
        alternativeStack.push(node);
      // Attach to next statement we encounter
      } else {
        if(alternativeStack.length) {
          node.alternatives = _.map(alternativeStack.reverse(), function(alt) {
            return alt.children;
          });
          alternativeStack = [];
        }
      }
      previous = node;
    });
    // Remove alternatives
    return cleanTree(top);
  },

  // Convert line into tree node
  parseLine: function(line) {
    // Statement
    if(line.indexOf('{{') == 0) {
      return this.parseStatement(line);
    // Text
    } else if(line.indexOf('|') == 0) {
      return {
        type: 'Text',
        content: line.slice(2) + '\n'
      };
    // Tag definition
    } else {
      return this.parseTag(line);
    }
  },

  // Parse all major components from a tag, including inline content
  parseTag: function(line) {
    var self = this;
    var m = line.match(/(([\w-#\.]+\s*>\s*)*)([\w-]+)?(#([\w-]+))?((\.[\w-]+)*)(\((.*)\))?(\.)?( (.*))?/);
    var multiTags = m[1];
    var tag = m[3] || 'div';
    var id = m[5];
    var classes = m[6] ? m[6].slice(1).split('.') : [];
    var inParens = m[9];
    var dot = m[10];
    var content = m[12];
    var attributes = {};
    var statements = [];
    // Parse attributes
    if(inParens) {
      var attrDefinitions;
      var i = inParens.indexOf('{{');
      if(i > -1) {
        attrDefinitions = inParens.slice(0, i);
        var statementDefinitions = inParens.slice(i, inParens.length);
        statements = self.parseMicroStatements(statementDefinitions);
      } else {
        attrDefinitions = inParens;
      }
      _.each(_.scan(attrDefinitions, /([\w-]+?)=(['"])(.+?)\2\s?/g), function(m) { // '
        var key = m[1];
        var value = m[3];
        attributes[key] = value;
      });
      // _.each(_.scan(statementDefinitions, /{{(\w+)\s+(\w+)\s+(\w+)}}/g), function(m) {
      //   statements[m[2]] = m[3];
      // });
    }
    // Make AST node
    var tag = {
      type: 'HTMLTag',
      tag: tag,
      id: id,
      classes: classes,
      attributes: attributes,
      content: content,
      statements: statements,
      slurpy: !!dot,
      children: []
    };
    // Build a hierarchy from multi tags
    if(multiTags) {
      // Remove trailing '>' before splitting
      multiTags = multiTags.trim().slice(0, -1);
      var tags = _.map(multiTags.split('>'), function(t) {
        return self.parseTag(t.trim());
      });
      var top = tags.shift();
      var prev = top;
      _.each(tags, function(t) {
        prev.children.push(t);
        prev = t;
      });
      prev.children.push(tag);
      return top;
    } else {
      return tag;
    }
  },

  parseMicroStatements: function(string) {
    var statements = [];
    _.each(_.scan(string, /{{(\w+)\s+(.*)}}/g), function(m) {
      var statement = m[1];
      var rest = m[2];
      if(statement == 'as') {
        statements.push({statement: 'as', varName: rest});
      } else if(statement == 'action') {
        var m = rest.match(/(\w+)\s+(\w+)/);
        statements.push({statement: 'action', event: m[1], method: m[2]});
      } else {
        throw('Unknown statement: ' + statement);
      }
    });
    return statements;
  },

  // Takes an statement line and creates the approriate node
  parseStatement: function(line) {
    var m;
    var out;
    // if-greater
    if(m = line.match(/{{if\s+(.+)\s+>\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-greater',
        path1: m[1],
        path2: m[2]
      };
    // if-equal
    } else if(m = line.match(/{{if\s+(.+)\s+==\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-equal',
        path1: m[1],
        path2: m[2]
      };
    // if-not-equal
    } else if(m = line.match(/{{if\s+(.+)\s+!=\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-not-equal',
        path1: m[1],
        path2: m[2]
      };
    // if
    } else if(m = line.match(/{{if\s+(.+)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if',
        path: m[1]
      };
    // for
    } else if(m = line.match(/{{for\s+((\w+)\s+in\s+)?(.+)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'for',
        itemPath: m[2],
        itemsPath: m[3]
      };
    // view
    } else if(m = line.match(/{{view\s+(\w+)?(\(.*\))?}}/)) {
      var parens = m[2];
      var args = parens ? _.map(parens.slice(1, -1).split(','), function(argument) {
        return argument.replace(/\s/g, '');
      }) : [];
      out = {
        type: 'Statement',
        keyword: 'view',
        viewModel: m[1],
        arguments: args
      };
    // import
    } else if(m = line.match(/{{import\s+(\w+)(\(.*\))?}}/)) {
      var parens = m[2];
      var args = {};
      if(parens) {
        _.each(parens.slice(1, -1).split(','), function(argument) {
          var pair = argument.replace(/\s/g, '').split(':');
          var vari = pair[0];
          var expr = pair[1];
          args[vari] = expr;
        });
      }
      out = {
        type: 'Statement',
        keyword: 'import',
        templateName: m[1] + '.tmpl',
        arguments: args
      };
    // content
    } else if(m = line.match(/{{content}}/)) {
      out = {
        type: 'Statement',
        keyword: 'content'
      };
    // client
    } else if(m = line.match(/{{client}}/)) {
      out = {
        type: 'Statement',
        keyword: 'client'
      };
    // route
    } else if(m = line.match(/{{route (.*)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'route',
        path: m[1]
      };
    // =>
    } else if(m = line.match(/{{=>}}/)) {
      out = {
        type: 'Alternative'
      };
    } else {
      throw('Unknown statement: ' + line);
    }
    out.children = [];
    return out;
  }
};

module.exports = Parser;
<|endoftext|>


<commit_before>/*
 * isStyleSupported
 * Provides a reliable means of determining support for CSS styles and values
 * @param {String} style The style you wish to test support for in camel case
 * @param {String} value The value of the style you wish to test support for (optional)
 * @return {Boolean} True or false based on results of the test
 */

(function(win){
	"use strict";

	//Define a test element
	var el = win.document.createElement('div'),
	//Create an array of vendor prefixes
	prefixes = ['Webkit', 'Moz', 'O', 'ms'],
	//Create a regular expression to make hypenated strings camel cased
	camelRe = /-([a-z]|[0-9])/ig,
	//Create the cache
	cache = {},
	//Define common variables
	length,
	support,
	camel,
	key,
	capitalized,
	hasValue;

	//Function to convert css notation (hypenated) to DOM notation (camel cased)
	function toCamelCase(style){
		return style.replace(camelRe, function(all, letter){
			return (letter + '').toUpperCase();
		});
	}

	//Due to differing APIs, we encapsulate the native implementations in this function
	function supports(style, value){
		//Check for the standard native method
		if('CSS' in win && 'supports' in win.CSS){
			return win.CSS.supports(style, value);
		}
		//Check for Opera's native method
		if('supportsCSS' in win){
			return win.supportsCSS(style, value);
		}
		return false;
	}

	//Define the function
	win.isStyleSupported = function(style, value){
		//Did the user provide the optional second argument of a style value
		hasValue = arguments.length === 2;
		//If no value is supplied we use "inherit" as all styles support it
		value = hasValue ? value : 'inherit';
		//Create a key for caching purposes
		key = style + ':' + value;
		//Check the cache
		if(key in cache){
			//If a value is found in the cache, return it
			return cache[key];
		}
		//Check native method first
		support = supports(style, value);
		//If the native method has determined positive support, skip the rest
		if(!support){
			//Create a camel cased version of the style property for DOM interaction
			camel = toCamelCase(style);
			//Create a capitalized version of the style property to be combined with the vendor prefixes
			capitalized = camel.charAt(0).toUpperCase() + camel.slice(1);
			//First determine support for the style
			support = camel in el.style;
			//check if the style is supported and no value was supplied
			if(support && !hasValue){
				//support is resolved, return true
				return true;
			}
			//Add the style and value inline to the test element
			el.style.cssText = style+':'+value;
			//Check to see if the style and value exists
			support = support && (el.style[camel] !== '');
			//Cache the length of prefixes for the while loop
			length = prefixes.length;
			//Loop the prefixes while support is yet to be determined
			while(!support && length--){
				//Create a vendor prefixed version of the style property in hypenated format
				style = '-'+prefixes[length].toLowerCase()+'-'+arguments[0];
				//Check native method first
				support = supports(style, value);
				//If the native method has determined positive support, skip the rest
				if(!support){
					//Create a vendor prefixed version of the style property in camel case format
					camel = prefixes[length] + capitalized;
					//First determine support for the style
					support = camel in el.style;
					//check if the style is supported and no value was supplied
					if(support && !hasValue){
						//support is resolved, return true
						return true;
					}
					//Add the vendor prefixed style and value inline
					el.style.cssText = style+':'+value;
					//Check to see if the style and value exists
					support = support && (el.style[camel] !== '');
				}
			}
		}
		//Cache and return the result
		return cache[key] = support;
	};

})(this);
<commit_msg>Optimized the detection process<commit_after>/*
 * isStyleSupported
 * Provides a reliable means of determining support for CSS styles and values
 * @param {String} style The style you wish to test support for in camel case
 * @param {String} value The value of the style you wish to test support for (optional)
 * @return {Boolean} True or false based on results of the test
 */

(function(win){
	"use strict";

	//Define a test element
	var el = win.document.createElement('div'),
	//Create an array of vendor prefixes
	prefixes = ['Webkit', 'Moz', 'O', 'ms'],
	//Create a regular expression to make hypenated strings camel cased
	camelRe = /-([a-z]|[0-9])/ig,
	//Create the cache
	cache = {},
	//Define common variables
	length,
	support,
	camel,
	key,
	capitalized,
	hasValue;

	//Function to convert css notation (hypenated) to DOM notation (camel cased)
	function toCamelCase(style){
		return style.replace(camelRe, function(all, letter){
			return (letter + '').toUpperCase();
		});
	}

	//Due to differing APIs, we encapsulate the native implementations in this function
	function supports(style, value){
		//Check for the standard native method
		if('CSS' in win && 'supports' in win.CSS){
			return win.CSS.supports(style, value);
		}
		//Check for Opera's native method
		if('supportsCSS' in win){
			return win.supportsCSS(style, value);
		}
		return false;
	}

	//Define the function
	win.isStyleSupported = function(style, value){
		//Did the user provide the optional second argument of a style value
		hasValue = arguments.length === 2;
		//If no value is supplied we use "inherit" as all styles support it
		value = hasValue ? value : 'inherit';
		//Create a key for caching purposes
		key = style + ':' + value;
		//Check the cache
		if(key in cache){
			//If a value is found in the cache, return it
			return cache[key];
		}
		//Check native method first
		support = supports(style, value);
		//If the native method has determined positive support, skip the rest
		if(!support){
			//Create a camel cased version of the style property for DOM interaction
			camel = toCamelCase(style);
			//Create a capitalized version of the style property to be combined with the vendor prefixes
			capitalized = camel.charAt(0).toUpperCase() + camel.slice(1);
			//First determine support for the style
			support = camel in el.style;
			//check if the style is supported and no value was supplied
			if(support && !hasValue){
				//support is resolved, return true
				return cache[key] = true;
			}
			//Add the style and value inline to the test element
			el.style.cssText = style+':'+value;
			//Check to see if the style and value exists
			support = support && (el.style[camel] !== '');
			//Cache the length of prefixes for the while loop
			length = prefixes.length;
			//Loop the prefixes while support is yet to be determined
			while(!support && length--){
				//Create a vendor prefixed version of the style property in hypenated format
				style = '-'+prefixes[length].toLowerCase()+'-'+arguments[0];
				//Check native method first
				support = supports(style, value);
				//If the native method has determined positive support, skip the rest
				if(!support){
					//Create a vendor prefixed version of the style property in camel case format
					camel = prefixes[length] + capitalized;
					//First determine support for the style
					support = camel in el.style;
					//check if the style is supported and no value was supplied
					if(support && !hasValue){
						//support is resolved, return true
						return cache[key] = true;
					}
					//Add the vendor prefixed style and value inline
					el.style.cssText = style+':'+value;
					//Check to see if the style and value exists
					support = support && (el.style[camel] !== '');
				}
			}
		}
		//Cache and return the result
		return cache[key] = support;
	};

})(this);
<|endoftext|>