Created
October 17, 2016 10:43
-
-
Save ayapi/e29c9982aacd0ca3f03b3b83e6c642e4 to your computer and use it in GitHub Desktop.
completion (doesnt work yet)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
const chevrotain = require('chevrotain'); | |
const Lexer = chevrotain.Lexer; | |
const Parser = chevrotain.Parser; | |
const extendToken = chevrotain.extendToken; | |
const KEYWORD = extendToken('KEYWORD', Lexer.NA); | |
const PRIVATE = extendToken('PRIVATE', /Private/i, KEYWORD); | |
const PUBLIC = extendToken('PUBLIC', /Public/i, KEYWORD); | |
const STATIC = extendToken('STATIC', /Static/i, KEYWORD); | |
const DECLARE = extendToken('DECLARE', /Declare/i, KEYWORD); | |
const ENUM = extendToken('ENUM', /Enum/i, KEYWORD); | |
const FUNCTION = extendToken('FUNCTION', /Function/i, KEYWORD); | |
const IDENTIFIER = extendToken("IDENTIFIER", /\w+/); | |
const _ = extendToken('_', /[ \t]+/); // WhiteSpace | |
const NL = extendToken('NL', /(\s*\r?\n+\s*)+/); | |
const allTokens = [_, NL, PRIVATE, PUBLIC, STATIC, ENUM, DECLARE, FUNCTION, IDENTIFIER]; | |
const LangLexer = new Lexer(allTokens); | |
class LangParser extends Parser { | |
constructor(input) { | |
super(input, allTokens, {recoveryEnabled: true}); | |
let $ = this; | |
// startRule -> NL? stmt (NL stmt)* NL? | |
this.startRule = $.RULE('startRule', () => { | |
$.OPTION1(() => $.CONSUME1(NL)); | |
$.AT_LEAST_ONE_SEP(NL, () => { | |
$.SUBRULE($.stmt); | |
}, 'stmt'); | |
$.OPTION2(() => $.CONSUME2(NL)); | |
}); | |
// stmt -> functionStmt | enumStmt | declareStmt | |
this.stmt = $.RULE('stmt', () => { | |
$.OR([ | |
{ALT: () => $.SUBRULE($.functionStmt)}, | |
{ALT: () => $.SUBRULE($.enumStmt)}, | |
{ALT: () => $.SUBRULE($.declareStmt)} | |
]); | |
}); | |
// functionStmt -> (visibility _)? (STATIC _)? FUNCTION _ IDENTIFIER | |
this.functionStmt = $.RULE('functionStmt', () => { | |
$.OPTION1(() => { | |
$.SUBRULE($.visibility); | |
$.CONSUME1(_); | |
}); | |
$.OPTION2(() => { | |
$.CONSUME(STATIC); | |
$.CONSUME2(_); | |
}); | |
$.CONSUME(FUNCTION); | |
$.CONSUME3(_); | |
$.CONSUME(IDENTIFIER); | |
}); | |
// enumStmt -> (visibility _)? (STATIC _)? ENUM _ IDENTIFIER | |
this.enumStmt = $.RULE('enumStmt', () => { | |
$.OPTION1(() => { | |
$.SUBRULE($.visibility); | |
$.CONSUME1(_); | |
}); | |
$.OPTION2(() => { | |
$.CONSUME(STATIC); | |
$.CONSUME2(_); | |
}); | |
$.CONSUME(ENUM); | |
$.CONSUME3(_); | |
$.CONSUME(IDENTIFIER); | |
}); | |
// declareStmt -> (visibility _)? DECLARE _ IDENTIFIER | |
this.declareStmt = $.RULE('declareStmt', () => { | |
$.OPTION(() => { | |
$.SUBRULE($.visibility); | |
$.CONSUME1(_); | |
}); | |
$.CONSUME(DECLARE); | |
$.CONSUME2(_); | |
$.CONSUME(IDENTIFIER); | |
}); | |
// visibility -> PRIVATE | PUBLIC | |
this.visibility = $.RULE('visibility', () => { | |
$.OR([ | |
{ALT: () => $.CONSUME(PRIVATE)}, | |
{ALT: () => $.CONSUME(PUBLIC)} | |
]); | |
}); | |
Parser.performSelfAnalysis(this); | |
} | |
} | |
class LangCompletionParser extends LangParser { | |
constructor(input, assistOffset) { | |
super(input); | |
this.assistOffset = assistOffset; | |
this.lastGrammarPath = { | |
ruleStack: [], | |
occurrenceStack: [], | |
lastTok: undefined, | |
lastTokOccurrence: undefined | |
}; | |
} | |
/** | |
* Overrides the protected Parser.prototype <consumeInternal> method | |
* To calculate the syntactic information related to content assist | |
* | |
* Will terminate the parser's execution once the assistOffset has been reached. | |
* | |
*/ | |
consumeInternal(tokClass, idx) { | |
var consumedToken; | |
var contentAssistPointReached = false; | |
var pathToTokenBeforeContentAssist; | |
var prefix = ""; | |
try { | |
this.lastGrammarPath = this.getCurrentGrammarPath(tokClass, idx); | |
consumedToken = super.consumeInternal(tokClass, idx); | |
var nextToken = this.NEXT_TOKEN(); | |
var nextTokenEndOffset = nextToken.startOffset + nextToken.image.length; | |
console.log('consumedToken: ', consumedToken); | |
console.log('nextToken: ', nextToken); | |
// no prefix scenario (SELECT age FROM ^) | |
if (consumedToken !== undefined && | |
// we have reached the end of the input without encountering the contentAssist offset | |
// this means the content assist offset is AFTER the input | |
(this.NEXT_TOKEN() instanceof chevrotain.EOF || | |
// we consumed the last token BEFORE the content assist of offset | |
this.NEXT_TOKEN().startOffset > this.assistOffset | |
)) { | |
// reached the content assist point AFTER consuming some token successfully. | |
contentAssistPointReached = true; | |
pathToTokenBeforeContentAssist = this.getCurrentGrammarPath(tokClass, idx); | |
} | |
// The prefix scenario (SELECT age FRO^) | |
else if (nextTokenEndOffset >= this.assistOffset && // going to reach or pass the assist offset. | |
nextToken.startOffset < this.assistOffset && | |
// only provide suggestions if it was requested after some word like(Ident/Keyword) prefix. | |
(nextToken instanceof IDENTIFIER || nextToken instanceof KEYWORD)) { | |
contentAssistPointReached = true; | |
prefix = nextToken.image.substring(0, this.assistOffset - nextToken.startOffset); | |
// we need the last grammar path and not the current one as we need to find out what TokenTypes the prefix | |
// may belong to, and not what may come after the Token the prefix belongs to. | |
pathToTokenBeforeContentAssist = this.lastGrammarPath; | |
} | |
return consumedToken | |
} finally { | |
// halt the parsing flow if we have reached the content assist point | |
if (contentAssistPointReached) { | |
var nextPossibleTokTypes = this.getNextPossibleTokenTypes(pathToTokenBeforeContentAssist); | |
let contentAssistEarlyExitError = new Error("Content Assist path found"); | |
contentAssistEarlyExitError.path = pathToTokenBeforeContentAssist; | |
contentAssistEarlyExitError.nextPossibleTokTypes = nextPossibleTokTypes; | |
contentAssistEarlyExitError.prefix = prefix; | |
//noinspection ThrowInsideFinallyBlockJS | |
throw contentAssistEarlyExitError; | |
} | |
} | |
} | |
} | |
function getSuggestions(text, offset) { | |
let lexResult = LangLexer.tokenize(text); | |
if (lexResult.errors.length >= 1) { | |
throw new Error("lexing errors detected"); | |
} | |
const parser = new LangCompletionParser(lexResult.tokens, offset); | |
try { | |
parser.startRule(); | |
} catch(err) { | |
if (err.message === 'Content Assist path found') { | |
let {path, nextPossibleTokTypes, prefix} = err; | |
let candidates = nextPossibleTokTypes | |
// .filter(tokType => { | |
// return new tokType('dummy') instanceof KEYWORD; | |
// }) | |
.map(keywordType => { | |
return keywordType.PATTERN.source; | |
}); | |
console.log('prefix: ', prefix); | |
console.log('candidates:', candidates); | |
console.log('path:', path); | |
return candidates; | |
} else { | |
throw err; | |
} | |
} | |
} | |
var input = `Private `; | |
let i = 0; | |
while(i < input.length + 1) { | |
console.log(); | |
console.log(i); | |
let candidates = getSuggestions(input, i); | |
i++; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment