function compileAction(lexer, ruleName, action) {
        if (!action) {
            return { token: '' };
        }
        else if (typeof (action) === 'string') {
            return action; // { token: action };
        }
        else if (action.token || action.token === '') {
            if (typeof (action.token) !== 'string') {
                monarchCommon.throwError(lexer, 'a \'token\' attribute must be of type string, in rule: ' + ruleName);
                return { token: '' };
            }
            else {
                // only copy specific typed fields (only happens once during compile Lexer)
                var newAction = { token: action.token };
                if (action.token.indexOf('$') >= 0) {
                    newAction.tokenSubst = true;
                }
                if (typeof (action.bracket) === 'string') {
                    if (action.bracket === '@open') {
                        newAction.bracket = 1 /* Open */;
                    }
                    else if (action.bracket === '@close') {
                        newAction.bracket = -1 /* Close */;
                    }
                    else {
                        monarchCommon.throwError(lexer, 'a \'bracket\' attribute must be either \'@open\' or \'@close\', in rule: ' + ruleName);
                    }
                }
                if (action.next) {
                    if (typeof (action.next) !== 'string') {
                        monarchCommon.throwError(lexer, 'the next state must be a string value in rule: ' + ruleName);
                    }
                    else {
                        var next = action.next;
                        if (!/^(@pop|@push|@popall)$/.test(next)) {
                            if (next[0] === '@') {
                                next = next.substr(1); // peel off starting @ sign
                            }
                            if (next.indexOf('$') < 0) {
                                if (!monarchCommon.stateExists(lexer, monarchCommon.substituteMatches(lexer, next, '', [], ''))) {
                                    monarchCommon.throwError(lexer, 'the next state \'' + action.next + '\' is not defined in rule: ' + ruleName);
                                }
                            }
                        }
                        newAction.next = next;
                    }
                }
                if (typeof (action.goBack) === 'number') {
                    newAction.goBack = action.goBack;
                }
                if (typeof (action.switchTo) === 'string') {
                    newAction.switchTo = action.switchTo;
                }
                if (typeof (action.log) === 'string') {
                    newAction.log = action.log;
                }
                if (typeof (action.nextEmbedded) === 'string') {
                    newAction.nextEmbedded = action.nextEmbedded;
                    lexer.usesEmbedded = true;
                }
                return newAction;
            }
        }
        else if (Array.isArray(action)) {
            var results = [];
            var idx;
            for (idx in action) {
                if (action.hasOwnProperty(idx)) {
                    results[idx] = compileAction(lexer, ruleName, action[idx]);
                }
            }
            return { group: results };
        }
        else if (action.cases) {
            // build an array of test cases
            var cases = [];
            // for each case, push a test function and result value
            var tkey;
            for (tkey in action.cases) {
                if (action.cases.hasOwnProperty(tkey)) {
                    var val = compileAction(lexer, ruleName, action.cases[tkey]);
                    // what kind of case
                    if (tkey === '@default' || tkey === '@' || tkey === '') {
                        cases.push({ test: null, value: val, name: tkey });
                    }
                    else if (tkey === '@eos') {
                        cases.push({ test: function (id, matches, state, eos) { return eos; }, value: val, name: tkey });
                    }
                    else {
                        cases.push(createGuard(lexer, ruleName, tkey, val)); // call separate function to avoid local variable capture
                    }
                }
            }
            // create a matching function
            var def = lexer.defaultToken;
            return {
                test: function (id, matches, state, eos) {
                    var idx;
                    for (idx in cases) {
                        if (cases.hasOwnProperty(idx)) {
                            var didmatch = (!cases[idx].test || cases[idx].test(id, matches, state, eos));
                            if (didmatch) {
                                return cases[idx].value;
                            }
                        }
                    }
                    return def;
                }
            };
        }
        else {
            monarchCommon.throwError(lexer, 'an action must be a string, an object with a \'token\' or \'cases\' attribute, or an array of actions; in rule: ' + ruleName);
            return '';
        }
    }
Compiles an action: i.e. optimize regular expressions and case matches
and do many sanity checks.

This is called only during compilation but if the lexer definition
contains user functions as actions (which is usually not allowed), then this
may be called during lexing. It is important therefore to compile common cases efficiently