Skip to content

Instantly share code, notes, and snippets.

@ayapi
Created October 17, 2016 10:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ayapi/e29c9982aacd0ca3f03b3b83e6c642e4 to your computer and use it in GitHub Desktop.
Save ayapi/e29c9982aacd0ca3f03b3b83e6c642e4 to your computer and use it in GitHub Desktop.
completion (doesnt work yet)
"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