function compile(languageId, json) {
        if (!json || typeof (json) !== 'object') {
            throw new Error('Monarch: expecting a language definition object');
        }
        // Create our lexer
        var lexer = {};
        lexer.languageId = languageId;
        lexer.noThrow = false; // raise exceptions during compilation
        lexer.maxStack = 100;
        // Set standard fields: be defensive about types
        lexer.start = string(json.start);
        lexer.ignoreCase = bool(json.ignoreCase, false);
        lexer.tokenPostfix = string(json.tokenPostfix, '.' + lexer.languageId);
        lexer.defaultToken = string(json.defaultToken, 'source', function () { monarchCommon.throwError(lexer, 'the \'defaultToken\' must be a string'); });
        lexer.usesEmbedded = false; // becomes true if we find a nextEmbedded action
        // For calling compileAction later on
        var lexerMin = json;
        lexerMin.languageId = languageId;
        lexerMin.ignoreCase = lexer.ignoreCase;
        lexerMin.noThrow = lexer.noThrow;
        lexerMin.usesEmbedded = lexer.usesEmbedded;
        lexerMin.stateNames = json.tokenizer;
        lexerMin.defaultToken = lexer.defaultToken;
        // Compile an array of rules into newrules where RegExp objects are created.
        function addRules(state, newrules, rules) {
            var idx;
            for (idx in rules) {
                if (rules.hasOwnProperty(idx)) {
                    var rule = rules[idx];
                    var include = rule.include;
                    if (include) {
                        if (typeof (include) !== 'string') {
                            monarchCommon.throwError(lexer, 'an \'include\' attribute must be a string at: ' + state);
                        }
                        if (include[0] === '@') {
                            include = include.substr(1); // peel off starting @
                        }
                        if (!json.tokenizer[include]) {
                            monarchCommon.throwError(lexer, 'include target \'' + include + '\' is not defined at: ' + state);
                        }
                        addRules(state + '.' + include, newrules, json.tokenizer[include]);
                    }
                    else {
                        var newrule = new Rule(state);
                        // Set up new rule attributes
                        if (Array.isArray(rule) && rule.length >= 1 && rule.length <= 3) {
                            newrule.setRegex(lexerMin, rule[0]);
                            if (rule.length >= 3) {
                                if (typeof (rule[1]) === 'string') {
                                    newrule.setAction(lexerMin, { token: rule[1], next: rule[2] });
                                }
                                else if (typeof (rule[1]) === 'object') {
                                    var rule1 = rule[1];
                                    rule1.next = rule[2];
                                    newrule.setAction(lexerMin, rule1);
                                }
                                else {
                                    monarchCommon.throwError(lexer, 'a next state as the last element of a rule can only be given if the action is either an object or a string, at: ' + state);
                                }
                            }
                            else {
                                newrule.setAction(lexerMin, rule[1]);
                            }
                        }
                        else {
                            if (!rule.regex) {
                                monarchCommon.throwError(lexer, 'a rule must either be an array, or an object with a \'regex\' or \'include\' field at: ' + state);
                            }
                            if (rule.name) {
                                newrule.name = string(rule.name);
                            }
                            if (rule.matchOnlyAtStart) {
                                newrule.matchOnlyAtLineStart = bool(rule.matchOnlyAtLineStart);
                            }
                            newrule.setRegex(lexerMin, rule.regex);
                            newrule.setAction(lexerMin, rule.action);
                        }
                        newrules.push(newrule);
                    }
                }
            }
        }
        // compile the tokenizer rules
        if (!json.tokenizer || typeof (json.tokenizer) !== 'object') {
            monarchCommon.throwError(lexer, 'a language definition must define the \'tokenizer\' attribute as an object');
        }
        lexer.tokenizer = [];
        var key;
        for (key in json.tokenizer) {
            if (json.tokenizer.hasOwnProperty(key)) {
                if (!lexer.start) {
                    lexer.start = key;
                }
                var rules = json.tokenizer[key];
                lexer.tokenizer[key] = new Array();
                addRules('tokenizer.' + key, lexer.tokenizer[key], rules);
            }
        }
        lexer.usesEmbedded = lexerMin.usesEmbedded; // can be set during compileAction
        // Set simple brackets
        if (json.brackets) {
            if (!(Array.isArray(json.brackets))) {
                monarchCommon.throwError(lexer, 'the \'brackets\' attribute must be defined as an array');
            }
        }
        else {
            json.brackets = [
                { open: '{', close: '}', token: 'delimiter.curly' },
                { open: '[', close: ']', token: 'delimiter.square' },
                { open: '(', close: ')', token: 'delimiter.parenthesis' },
                { open: '<', close: '>', token: 'delimiter.angle' }
            ];
        }
        var brackets = [];
        for (var bracketIdx in json.brackets) {
            if (json.brackets.hasOwnProperty(bracketIdx)) {
                var desc = json.brackets[bracketIdx];
                if (desc && Array.isArray(desc) && desc.length === 3) {
                    desc = { token: desc[2], open: desc[0], close: desc[1] };
                }
                if (desc.open === desc.close) {
                    monarchCommon.throwError(lexer, 'open and close brackets in a \'brackets\' attribute must be different: ' + desc.open +
                        '\n hint: use the \'bracket\' attribute if matching on equal brackets is required.');
                }
                if (typeof (desc.open) === 'string' && typeof (desc.token) === 'string') {
                    brackets.push({
                        token: string(desc.token) + lexer.tokenPostfix,
                        open: monarchCommon.fixCase(lexer, string(desc.open)),
                        close: monarchCommon.fixCase(lexer, string(desc.close))
                    });
                }
                else {
                    monarchCommon.throwError(lexer, 'every element in the \'brackets\' array must be a \'{open,close,token}\' object or array');
                }
            }
        }
        lexer.brackets = brackets;
        // Disable throw so the syntax highlighter goes, no matter what
        lexer.noThrow = true;
        return lexer;
    }
Compiles a json description function into json where all regular expressions,
case matches etc, are compiled and all include rules are expanded.
We also compile the bracket definitions, supply defaults, and do many sanity checks.
If the 'jsonStrict' parameter is 'false', we allow at certain locations
regular expression objects and functions that get called during lexing.
(Currently we have no samples that need this so perhaps we should always have
jsonStrict to true).