Răsfoiți Sursa

HUE-6815 [frontend] Create a custom parser for global search autocompletion

Johan Ahlen 8 ani în urmă
părinte
comite
0c22475ee8

+ 695 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/globalSearchParser.js

@@ -0,0 +1,695 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License./* parser generated by jison 0.4.17 */
+/*
+  Returns a Parser object of the following structure:
+
+  Parser: {
+    yy: {}
+  }
+
+  Parser.prototype: {
+    yy: {},
+    trace: function(),
+    symbols_: {associative list: name ==> number},
+    terminals_: {associative list: number ==> name},
+    productions_: [...],
+    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
+    table: [...],
+    defaultActions: {...},
+    parseError: function(str, hash),
+    parse: function(input),
+
+    lexer: {
+        EOF: 1,
+        parseError: function(str, hash),
+        setInput: function(input),
+        input: function(),
+        unput: function(str),
+        more: function(),
+        less: function(n),
+        pastInput: function(),
+        upcomingInput: function(),
+        showPosition: function(),
+        test_match: function(regex_match_array, rule_index),
+        next: function(),
+        lex: function(),
+        begin: function(condition),
+        popState: function(),
+        _currentRules: function(),
+        topState: function(),
+        pushState: function(condition),
+
+        options: {
+            ranges: boolean           (optional: true ==> token location info will include a .range[] member)
+            flex: boolean             (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
+            backtrack_lexer: boolean  (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
+        },
+
+        performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
+        rules: [...],
+        conditions: {associative list: name ==> set},
+    }
+  }
+
+
+  token location info (@$, _$, etc.): {
+    first_line: n,
+    last_line: n,
+    first_column: n,
+    last_column: n,
+    range: [start_number, end_number]       (where the numbers are indexes into the input string, regular zero-based)
+  }
+
+
+  the parseError function receives a 'hash' object with these members for lexer and parser errors: {
+    text:        (matched text)
+    token:       (the produced terminal token, if any)
+    line:        (yylineno)
+  }
+  while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
+    loc:         (yylloc)
+    expected:    (string describing the set of expected tokens)
+    recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
+  }
+*/
+var globalSearchParser = (function(){
+var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,11],$V1=[1,14],$V2=[1,12],$V3=[1,16],$V4=[5,13,14,15,18],$V5=[1,22],$V6=[1,23],$V7=[5,13,15,18],$V8=[1,27],$V9=[1,26];
+var parser = {trace: function trace() { },
+yy: {},
+symbols_: {"error":2,"GlobalSearchAutocomplete":3,"SearchParts":4,"EOF":5,"SearchParts_EDIT":6,"SearchPart":7,"SearchPart_EDIT":8,"Facet":9,"FreeText":10,"Facet_EDIT":11,"FreeText_EDIT":12,"FACET":13,"CURSOR":14,"TEXT":15,"QuotedValue":16,"QuotedValue_EDIT":17,"QUOTE":18,"VALUE":19,"PARTIAL_VALUE":20,"$accept":0,"$end":1},
+terminals_: {2:"error",5:"EOF",13:"FACET",14:"CURSOR",15:"TEXT",18:"QUOTE",19:"VALUE",20:"PARTIAL_VALUE"},
+productions_: [0,[3,2],[3,2],[3,1],[4,1],[4,2],[6,1],[6,2],[6,2],[6,3],[7,1],[7,1],[8,1],[8,1],[9,2],[11,2],[10,1],[10,1],[12,1],[12,1],[16,3],[16,2],[17,2]],
+performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
+/* this == yyval */
+
+var $0 = $$.length - 1;
+switch (yystate) {
+case 1: case 3:
+
+     return {};
+   
+break;
+case 2:
+
+     if (!$$[$0-1].facets) {
+       $$[$0-1].facets = [];
+     }
+     return $$[$0-1];
+   
+break;
+case 4:
+
+     this.$ = { facets: $$[$0].facet ? [ $$[$0].facet ] : [] }
+   
+break;
+case 5:
+
+     if ($$[$0].facet) {
+       $$[$0-1].facets.push($$[$0].facet);
+     }
+   
+break;
+case 7:
+
+     $$[$0].facets = $$[$0-1].facets;
+     this.$ = $$[$0];
+   
+break;
+case 8:
+
+     $$[$0-1].facets = $$[$0].facets;
+   
+break;
+case 9:
+
+     $$[$0-1].facets = $$[$0-2].facets.concat($$[$0].facets);
+     this.$ = $$[$0-1];
+   
+break;
+case 14:
+
+     this.$ = { facet: $$[$0-1].substring(0, $$[$0-1].length - 1).trim() };
+   
+break;
+case 18:
+this.$ = { suggestFacets: true };
+break;
+}
+},
+table: [{3:1,4:2,5:[1,4],6:3,7:5,8:6,9:7,10:8,11:9,12:10,13:$V0,14:$V1,15:$V2,16:13,17:15,18:$V3},{1:[3]},{5:[1,17],7:18,8:19,9:7,10:8,11:9,12:10,13:$V0,14:$V1,15:$V2,16:13,17:15,18:$V3},{5:[1,20]},{1:[2,3]},o($V4,[2,4]),{4:21,5:[2,6],7:5,9:7,10:8,13:$V5,15:$V2,16:13,18:$V6},o($V4,[2,10]),o($V4,[2,11]),o($V7,[2,12]),o($V7,[2,13]),{10:24,14:[1,25],15:$V2,16:13,18:$V6},o($V4,[2,16]),o($V4,[2,17]),o($V7,[2,18]),o($V7,[2,19]),{18:$V8,19:$V9,20:[1,28]},{1:[2,1]},o($V4,[2,5]),{4:29,5:[2,7],7:5,9:7,10:8,13:$V5,15:$V2,16:13,18:$V6},{1:[2,2]},{5:[2,8],7:18,9:7,10:8,13:$V5,15:$V2,16:13,18:$V6},{10:24,15:$V2,16:13,18:$V6},{18:$V8,19:$V9},o($V4,[2,14]),o($V7,[2,15]),{18:[1,30]},o($V4,[2,21]),o($V7,[2,22]),{5:[2,9],7:18,9:7,10:8,13:$V5,15:$V2,16:13,18:$V6},o($V4,[2,20])],
+defaultActions: {4:[2,3],17:[2,1],20:[2,2]},
+parseError: function parseError(str, hash) {
+    if (hash.recoverable) {
+        this.trace(str);
+    } else {
+        function _parseError (msg, hash) {
+            this.message = msg;
+            this.hash = hash;
+        }
+        _parseError.prototype = Error;
+
+        throw new _parseError(str, hash);
+    }
+},
+parse: function parse(input) {
+    var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
+    var args = lstack.slice.call(arguments, 1);
+    var lexer = Object.create(this.lexer);
+    var sharedState = { yy: {} };
+    for (var k in this.yy) {
+        if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
+            sharedState.yy[k] = this.yy[k];
+        }
+    }
+    lexer.setInput(input, sharedState.yy);
+    sharedState.yy.lexer = lexer;
+    sharedState.yy.parser = this;
+    if (typeof lexer.yylloc == 'undefined') {
+        lexer.yylloc = {};
+    }
+    var yyloc = lexer.yylloc;
+    lstack.push(yyloc);
+    var ranges = lexer.options && lexer.options.ranges;
+    if (typeof sharedState.yy.parseError === 'function') {
+        this.parseError = sharedState.yy.parseError;
+    } else {
+        this.parseError = Object.getPrototypeOf(this).parseError;
+    }
+    function popStack(n) {
+        stack.length = stack.length - 2 * n;
+        vstack.length = vstack.length - n;
+        lstack.length = lstack.length - n;
+    }
+    _token_stack:
+        var lex = function () {
+            var token;
+            token = lexer.lex() || EOF;
+            if (typeof token !== 'number') {
+                token = self.symbols_[token] || token;
+            }
+            return token;
+        };
+    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
+    while (true) {
+        state = stack[stack.length - 1];
+        if (this.defaultActions[state]) {
+            action = this.defaultActions[state];
+        } else {
+            if (symbol === null || typeof symbol == 'undefined') {
+                symbol = lex();
+            }
+            action = table[state] && table[state][symbol];
+        }
+                    if (typeof action === 'undefined' || !action.length || !action[0]) {
+                var errStr = '';
+                expected = [];
+                for (p in table[state]) {
+                    if (this.terminals_[p] && p > TERROR) {
+                        expected.push('\'' + this.terminals_[p] + '\'');
+                    }
+                }
+                if (lexer.showPosition) {
+                    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
+                } else {
+                    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
+                }
+                this.parseError(errStr, {
+                    text: lexer.match,
+                    token: this.terminals_[symbol] || symbol,
+                    line: lexer.yylineno,
+                    loc: yyloc,
+                    expected: expected
+                });
+            }
+        if (action[0] instanceof Array && action.length > 1) {
+            throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
+        }
+        switch (action[0]) {
+        case 1:
+            stack.push(symbol);
+            vstack.push(lexer.yytext);
+            lstack.push(lexer.yylloc);
+            stack.push(action[1]);
+            symbol = null;
+            if (!preErrorSymbol) {
+                yyleng = lexer.yyleng;
+                yytext = lexer.yytext;
+                yylineno = lexer.yylineno;
+                yyloc = lexer.yylloc;
+                if (recovering > 0) {
+                    recovering--;
+                }
+            } else {
+                symbol = preErrorSymbol;
+                preErrorSymbol = null;
+            }
+            break;
+        case 2:
+            len = this.productions_[action[1]][1];
+            yyval.$ = vstack[vstack.length - len];
+            yyval._$ = {
+                first_line: lstack[lstack.length - (len || 1)].first_line,
+                last_line: lstack[lstack.length - 1].last_line,
+                first_column: lstack[lstack.length - (len || 1)].first_column,
+                last_column: lstack[lstack.length - 1].last_column
+            };
+            if (ranges) {
+                yyval._$.range = [
+                    lstack[lstack.length - (len || 1)].range[0],
+                    lstack[lstack.length - 1].range[1]
+                ];
+            }
+            r = this.performAction.apply(yyval, [
+                yytext,
+                yyleng,
+                yylineno,
+                sharedState.yy,
+                action[1],
+                vstack,
+                lstack
+            ].concat(args));
+            if (typeof r !== 'undefined') {
+                return r;
+            }
+            if (len) {
+                stack = stack.slice(0, -1 * len * 2);
+                vstack = vstack.slice(0, -1 * len);
+                lstack = lstack.slice(0, -1 * len);
+            }
+            stack.push(this.productions_[action[1]][0]);
+            vstack.push(yyval.$);
+            lstack.push(yyval._$);
+            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+            stack.push(newState);
+            break;
+        case 3:
+            return true;
+        }
+    }
+    return true;
+}};
+
+
+SqlParseSupport.initGlobalSearchParser(parser);/* generated by jison-lex 0.3.4 */
+var lexer = (function(){
+var lexer = ({
+
+EOF:1,
+
+parseError:function parseError(str, hash) {
+        if (this.yy.parser) {
+            this.yy.parser.parseError(str, hash);
+        } else {
+            throw new Error(str);
+        }
+    },
+
+// resets the lexer, sets new input
+setInput:function (input, yy) {
+        this.yy = yy || this.yy || {};
+        this._input = input;
+        this._more = this._backtrack = this.done = false;
+        this.yylineno = this.yyleng = 0;
+        this.yytext = this.matched = this.match = '';
+        this.conditionStack = ['INITIAL'];
+        this.yylloc = {
+            first_line: 1,
+            first_column: 0,
+            last_line: 1,
+            last_column: 0
+        };
+        if (this.options.ranges) {
+            this.yylloc.range = [0,0];
+        }
+        this.offset = 0;
+        return this;
+    },
+
+// consumes and returns one char from the input
+input:function () {
+        var ch = this._input[0];
+        this.yytext += ch;
+        this.yyleng++;
+        this.offset++;
+        this.match += ch;
+        this.matched += ch;
+        var lines = ch.match(/(?:\r\n?|\n).*/g);
+        if (lines) {
+            this.yylineno++;
+            this.yylloc.last_line++;
+        } else {
+            this.yylloc.last_column++;
+        }
+        if (this.options.ranges) {
+            this.yylloc.range[1]++;
+        }
+
+        this._input = this._input.slice(1);
+        return ch;
+    },
+
+// unshifts one char (or a string) into the input
+unput:function (ch) {
+        var len = ch.length;
+        var lines = ch.split(/(?:\r\n?|\n)/g);
+
+        this._input = ch + this._input;
+        this.yytext = this.yytext.substr(0, this.yytext.length - len);
+        //this.yyleng -= len;
+        this.offset -= len;
+        var oldLines = this.match.split(/(?:\r\n?|\n)/g);
+        this.match = this.match.substr(0, this.match.length - 1);
+        this.matched = this.matched.substr(0, this.matched.length - 1);
+
+        if (lines.length - 1) {
+            this.yylineno -= lines.length - 1;
+        }
+        var r = this.yylloc.range;
+
+        this.yylloc = {
+            first_line: this.yylloc.first_line,
+            last_line: this.yylineno + 1,
+            first_column: this.yylloc.first_column,
+            last_column: lines ?
+                (lines.length === oldLines.length ? this.yylloc.first_column : 0)
+                 + oldLines[oldLines.length - lines.length].length - lines[0].length :
+              this.yylloc.first_column - len
+        };
+
+        if (this.options.ranges) {
+            this.yylloc.range = [r[0], r[0] + this.yyleng - len];
+        }
+        this.yyleng = this.yytext.length;
+        return this;
+    },
+
+// When called from action, caches matched text and appends it on next action
+more:function () {
+        this._more = true;
+        return this;
+    },
+
+// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
+reject:function () {
+        if (this.options.backtrack_lexer) {
+            this._backtrack = true;
+        } else {
+            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
+                text: "",
+                token: null,
+                line: this.yylineno
+            });
+
+        }
+        return this;
+    },
+
+// retain first n characters of the match
+less:function (n) {
+        this.unput(this.match.slice(n));
+    },
+
+// displays already matched input, i.e. for error messages
+pastInput:function () {
+        var past = this.matched.substr(0, this.matched.length - this.match.length);
+        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+    },
+
+// displays upcoming input, i.e. for error messages
+upcomingInput:function () {
+        var next = this.match;
+        if (next.length < 20) {
+            next += this._input.substr(0, 20-next.length);
+        }
+        return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
+    },
+
+// displays the character position where the lexing error occurred, i.e. for error messages
+showPosition:function () {
+        var pre = this.pastInput();
+        var c = new Array(pre.length + 1).join("-");
+        return pre + this.upcomingInput() + "\n" + c + "^";
+    },
+
+// test the lexed token: return FALSE when not a match, otherwise return token
+test_match:function (match, indexed_rule) {
+        var token,
+            lines,
+            backup;
+
+        if (this.options.backtrack_lexer) {
+            // save context
+            backup = {
+                yylineno: this.yylineno,
+                yylloc: {
+                    first_line: this.yylloc.first_line,
+                    last_line: this.last_line,
+                    first_column: this.yylloc.first_column,
+                    last_column: this.yylloc.last_column
+                },
+                yytext: this.yytext,
+                match: this.match,
+                matches: this.matches,
+                matched: this.matched,
+                yyleng: this.yyleng,
+                offset: this.offset,
+                _more: this._more,
+                _input: this._input,
+                yy: this.yy,
+                conditionStack: this.conditionStack.slice(0),
+                done: this.done
+            };
+            if (this.options.ranges) {
+                backup.yylloc.range = this.yylloc.range.slice(0);
+            }
+        }
+
+        lines = match[0].match(/(?:\r\n?|\n).*/g);
+        if (lines) {
+            this.yylineno += lines.length;
+        }
+        this.yylloc = {
+            first_line: this.yylloc.last_line,
+            last_line: this.yylineno + 1,
+            first_column: this.yylloc.last_column,
+            last_column: lines ?
+                         lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
+                         this.yylloc.last_column + match[0].length
+        };
+        this.yytext += match[0];
+        this.match += match[0];
+        this.matches = match;
+        this.yyleng = this.yytext.length;
+        if (this.options.ranges) {
+            this.yylloc.range = [this.offset, this.offset += this.yyleng];
+        }
+        this._more = false;
+        this._backtrack = false;
+        this._input = this._input.slice(match[0].length);
+        this.matched += match[0];
+        token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
+        if (this.done && this._input) {
+            this.done = false;
+        }
+        if (token) {
+            return token;
+        } else if (this._backtrack) {
+            // recover context
+            for (var k in backup) {
+                this[k] = backup[k];
+            }
+            return false; // rule action called reject() implying the next rule should be tested instead.
+        }
+        return false;
+    },
+
+// return next match in input
+next:function () {
+        if (this.done) {
+            return this.EOF;
+        }
+        if (!this._input) {
+            this.done = true;
+        }
+
+        var token,
+            match,
+            tempMatch,
+            index;
+        if (!this._more) {
+            this.yytext = '';
+            this.match = '';
+        }
+        var rules = this._currentRules();
+        for (var i = 0; i < rules.length; i++) {
+            tempMatch = this._input.match(this.rules[rules[i]]);
+            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+                match = tempMatch;
+                index = i;
+                if (this.options.backtrack_lexer) {
+                    token = this.test_match(tempMatch, rules[i]);
+                    if (token !== false) {
+                        return token;
+                    } else if (this._backtrack) {
+                        match = false;
+                        continue; // rule action called reject() implying a rule MISmatch.
+                    } else {
+                        // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+                        return false;
+                    }
+                } else if (!this.options.flex) {
+                    break;
+                }
+            }
+        }
+        if (match) {
+            token = this.test_match(match, rules[index]);
+            if (token !== false) {
+                return token;
+            }
+            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+            return false;
+        }
+        if (this._input === "") {
+            return this.EOF;
+        } else {
+            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
+                text: "",
+                token: null,
+                line: this.yylineno
+            });
+        }
+    },
+
+// return next match that has a token
+lex:function lex() {
+        var r = this.next();
+        if (r) {
+            return r;
+        } else {
+            return this.lex();
+        }
+    },
+
+// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
+begin:function begin(condition) {
+        this.conditionStack.push(condition);
+    },
+
+// pop the previously active lexer condition state off the condition stack
+popState:function popState() {
+        var n = this.conditionStack.length - 1;
+        if (n > 0) {
+            return this.conditionStack.pop();
+        } else {
+            return this.conditionStack[0];
+        }
+    },
+
+// produce the lexer rule set which is active for the currently active lexer condition state
+_currentRules:function _currentRules() {
+        if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
+            return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
+        } else {
+            return this.conditions["INITIAL"].rules;
+        }
+    },
+
+// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
+topState:function topState(n) {
+        n = this.conditionStack.length - 1 - Math.abs(n || 0);
+        if (n >= 0) {
+            return this.conditionStack[n];
+        } else {
+            return "INITIAL";
+        }
+    },
+
+// alias for begin(condition)
+pushState:function pushState(condition) {
+        this.begin(condition);
+    },
+
+// return the number of states currently on the stack
+stateStackSize:function stateStackSize() {
+        return this.conditionStack.length;
+    },
+options: {},
+performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+var YYSTATE=YY_START;
+switch($avoiding_name_collisions) {
+case 0: parser.yy.cursorFound = yy_.yylloc; return 14; 
+break;
+case 1: return 13 
+break;
+case 2: this.begin('singleQuote'); return 18; 
+break;
+case 3:
+                                                  if (parser.handleQuotedValueWithCursor(this, yy_.yytext, yy_.yylloc, '\'')) {
+                                                    return 20;
+                                                  }
+                                                  return 19;
+                                                
+break;
+case 4: this.popState(); return 18; 
+break;
+case 5: this.begin('doubleQuote'); return 18; 
+break;
+case 6:
+                                                  if (parser.handleQuotedValueWithCursor(this, yy_.yytext, yy_.yylloc, '"')) {
+                                                    return 20;
+                                                  }
+                                                  return 19;
+                                                
+break;
+case 7: this.popState(); return 18; 
+break;
+case 8: return 15; 
+break;
+case 9: return 5; 
+break;
+}
+},
+rules: [/^(?:\u2020)/,/^(?:[a-zA-Z]+\s*[:])/,/^(?:')/,/^(?:(?:\\[']|[^'])+)/,/^(?:')/,/^(?:")/,/^(?:(?:\\["]|[^"])+)/,/^(?:")/,/^(?:[^"'\u2020]+)/,/^(?:$)/],
+conditions: {"singleQuote":{"rules":[3,4],"inclusive":false},"doubleQuote":{"rules":[6,7],"inclusive":false},"INITIAL":{"rules":[0,1,2,5,8,9],"inclusive":true}}
+});
+return lexer;
+})();
+parser.lexer = lexer;
+function Parser () {
+  this.yy = {};
+}
+Parser.prototype = parser;parser.Parser = Parser;
+return new Parser;
+})();
+
+
+if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
+exports.parser = globalSearchParser;
+exports.Parser = globalSearchParser.Parser;
+exports.parse = function () { return globalSearchParser.parse.apply(globalSearchParser, arguments); };
+exports.main = function commonjsMain(args) {
+    if (!args[1]) {
+        console.log('Usage: '+args[0]+' FILE');
+        process.exit(1);
+    }
+    var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
+    return exports.parser.parse(source);
+};
+if (typeof module !== 'undefined' && require.main === module) {
+  exports.main(process.argv.slice(1));
+}
+}

+ 148 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/jison/globalSearchParser.jison

@@ -0,0 +1,148 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+%lex
+%x singleQuote doubleQuote
+%%
+
+'\u2020'                                        { parser.yy.cursorFound = yylloc; return 'CURSOR'; }
+
+[a-zA-Z]+\s*[:]                                 { return 'FACET' }
+
+\'                                              { this.begin('singleQuote'); return 'QUOTE'; }
+
+<singleQuote>(?:\\[']|[^'])+                    {
+                                                  if (parser.handleQuotedValueWithCursor(this, yytext, yylloc, '\'')) {
+                                                    return 'PARTIAL_VALUE';
+                                                  }
+                                                  return 'VALUE';
+                                                }
+
+<singleQuote>\'                                 { this.popState(); return 'QUOTE'; }
+
+\"                                              { this.begin('doubleQuote'); return 'QUOTE'; }
+
+<doubleQuote>(?:\\["]|[^"])+                    {
+                                                  if (parser.handleQuotedValueWithCursor(this, yytext, yylloc, '"')) {
+                                                    return 'PARTIAL_VALUE';
+                                                  }
+                                                  return 'VALUE';
+                                                }
+
+<doubleQuote>\"                                 { this.popState(); return 'QUOTE'; }
+
+[^"'\u2020]+                                    { return 'TEXT'; }
+
+<<EOF>>                                         { return 'EOF'; }
+
+/lex
+
+%start GlobalSearchAutocomplete
+
+%%
+
+GlobalSearchAutocomplete
+ : SearchParts 'EOF'
+   {
+     return {};
+   }
+ | SearchParts_EDIT 'EOF'
+   {
+     if (!$1.facets) {
+       $1.facets = [];
+     }
+     return $1;
+   }
+ | 'EOF'
+   {
+     return {};
+   }
+ ;
+
+SearchParts
+ : SearchPart
+   {
+     $$ = { facets: $1.facet ? [ $1.facet ] : [] }
+   }
+ | SearchParts SearchPart
+   {
+     if ($2.facet) {
+       $1.facets.push($2.facet);
+     }
+   }
+ ;
+
+SearchParts_EDIT
+ : SearchPart_EDIT
+ | SearchParts SearchPart_EDIT
+   {
+     $2.facets = $1.facets;
+     $$ = $2;
+   }
+ | SearchPart_EDIT SearchParts
+   {
+     $1.facets = $2.facets;
+   }
+ | SearchParts SearchPart_EDIT SearchParts
+   {
+     $2.facets = $1.facets.concat($3.facets);
+     $$ = $2;
+   }
+ ;
+
+SearchPart
+ : Facet
+ | FreeText
+ ;
+
+SearchPart_EDIT
+ : Facet_EDIT
+ | FreeText_EDIT
+ ;
+
+Facet
+ : 'FACET' FreeText
+   {
+     $$ = { facet: $1.substring(0, $1.length - 1).trim() };
+   }
+ ;
+
+Facet_EDIT
+ : 'FACET' 'CURSOR'
+ ;
+
+FreeText
+ : 'TEXT'
+ | QuotedValue
+ ;
+
+FreeText_EDIT
+ : 'CURSOR'             --> { suggestFacets: true }
+ | QuotedValue_EDIT
+ ;
+
+QuotedValue
+ : 'QUOTE' 'VALUE' 'QUOTE'
+ | 'QUOTE' 'QUOTE'
+ ;
+
+QuotedValue_EDIT
+ : 'QUOTE' 'PARTIAL_VALUE'
+ ;
+
+%%
+
+SqlParseSupport.initGlobalSearchParser(parser);

+ 39 - 2
desktop/core/src/desktop/static/desktop/js/autocomplete/sqlParseSupport.js

@@ -1543,7 +1543,6 @@ var SqlParseSupport = (function () {
     };
   };
 
-
   var SYNTAX_PARSER_NOOP_FUNCTIONS = ['prepareNewStatement', 'addCommonTableExpressions', 'pushQueryState', 'popQueryState', 'suggestSelectListAliases',
     'isHive', 'isImpala', 'mergeSuggestKeywords', 'suggestValueExpressionKeywords', 'getValueExpressionKeywords', 'getTypeKeywords',
     'getColumnDataTypeKeywords', 'addColRefIfExists', 'selectListNoTableSuggest', 'suggestJoinConditions', 'suggestJoins', 'valueExpressionSuggest',
@@ -1664,9 +1663,47 @@ var SqlParseSupport = (function () {
     }
   };
 
+  var initGlobalSearchParser = function (parser) {
+
+    parser.identifyPartials = function (beforeCursor, afterCursor) {
+      var beforeMatch = beforeCursor.match(/[0-9a-zA-Z_]*$/);
+      var afterMatch = afterCursor.match(/^[0-9a-zA-Z_]*(?:\((?:[^)]*\))?)?/);
+      return {left: beforeMatch ? beforeMatch[0].length : 0, right: afterMatch ? afterMatch[0].length : 0};
+    };
+
+    parser.parseGlobalSearch = function (beforeCursor, afterCursor, debug) {
+      delete parser.yy.cursorFound;
+
+      parser.yy.partialLengths = parser.identifyPartials(beforeCursor, afterCursor);
+
+      if (parser.yy.partialLengths.left > 0) {
+        beforeCursor = beforeCursor.substring(0, beforeCursor.length - parser.yy.partialLengths.left);
+      }
+
+      if (parser.yy.partialLengths.right > 0) {
+        afterCursor = afterCursor.substring(parser.yy.partialLengths.right);
+      }
+
+      var result;
+      try {
+        // \u2020 represents the cursor, \u2021 represent partial
+        // TODO: Handle partial cursor
+        result = parser.parse(beforeCursor + '\u2020' + afterCursor);
+      } catch (err) {
+        if (debug) {
+          console.log(err);
+          console.error(err.stack);
+          console.log(parser.yy.error);
+        }
+      }
+      return result;
+    };
+  };
+
   return {
     initSqlParser: initSqlParser,
     initSyntaxParser: initSyntaxParser,
-    stringDistance: stringDistance
+    stringDistance: stringDistance,
+    initGlobalSearchParser: initGlobalSearchParser
   };
 })();

+ 38 - 0
desktop/core/src/desktop/static/desktop/spec/autocomplete/globalSearchParserSpec.js

@@ -0,0 +1,38 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function () {
+  describe('globalSearchParser.js', function () {
+
+    var testParser = function (beforeCursor, afterCursor, expectedResult) {
+      var result = globalSearchParser.parseGlobalSearch(beforeCursor, afterCursor, true);
+      expect(result).toEqual(expectedResult);
+    };
+
+    it('should handle "|"', function () {
+      testParser('', '', {
+        suggestFacets: true,
+        facets: [ ]
+      });
+    });
+
+    it('should handle "TAGS: asdf |"', function () {
+      testParser('TAGS: asdf ', '', {
+        suggestFacets: true,
+        facets: [ 'TAGS' ]
+      });
+    });
+  });
+})();

+ 3 - 0
desktop/core/src/desktop/templates/jasmineRunner.html

@@ -128,6 +128,9 @@
   <script type="text/javascript" src="../static/desktop/js/autocomplete/sqlSyntaxParser.js"></script>
   <script type="text/javascript" src="../static/desktop/js/autocomplete/spec/sqlSyntaxParserSpec.js"></script>
 
+  <script type="text/javascript" src="../static/desktop/js/autocomplete/globalSearchParser.js"></script>
+  <script type="text/javascript" src="../static/desktop/spec/autocomplete/globalSearchParserSpec.js"></script>
+
   <script type="text/javascript" charset="utf-8">
     if (/PhantomJS/.test(window.navigator.userAgent)) {
       console.log("Running Jasmine tests with JUnitXmlReporter and TerminalReporter");

+ 6 - 0
tools/jison/hue-jison.sh

@@ -56,5 +56,11 @@ jison sqlStatementsParser.jison
 cat license.txt sqlStatementsParser.js > ../sqlStatementsParser.js
 rm sqlStatementsParser.js
 
+# === Global Search parser ===
+echo "Creating Global Search parser..."
+jison globalSearchParser.jison
+cat license.txt globalSearchParser.js > ../globalSearchParser.js
+rm globalSearchParser.js
+
 popd
 echo "Done!"