syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
229 lines (186 loc) • 5.53 kB
JavaScript
/**
* Generic tokenizer used by the parser in the Syntax tool.
*
* https://www.npmjs.com/package/syntax-cli
*
* See `--custom-tokinzer` to skip this generation, and use a custom one.
*/
const lexRules = {{{LEX_RULES}}};
const lexRulesByConditions = {{{LEX_RULES_BY_START_CONDITIONS}}};
const EOF_TOKEN = {
type: EOF,
value: '',
};
tokenizer = {
initString(string) {
this._string = string;
this._cursor = 0;
this._states = ['INITIAL'];
this._tokensQueue = [];
this._currentLine = 1;
this._currentColumn = 0;
this._currentLineBeginOffset = 0;
/**
* Matched token location data.
*/
this._tokenStartOffset = 0;
this._tokenEndOffset = 0;
this._tokenStartLine = 1;
this._tokenEndLine = 1;
this._tokenStartColumn = 0;
this._tokenEndColumn = 0;
return this;
},
/**
* Returns tokenizer states.
*/
getStates() {
return this._states;
},
getCurrentState() {
return this._states[this._states.length - 1];
},
pushState(state) {
this._states.push(state);
},
begin(state) {
this.pushState(state);
},
popState() {
if (this._states.length > 1) {
return this._states.pop();
}
return this._states[0];
},
getNextToken() {
// Something was queued, return it.
if (this._tokensQueue.length > 0) {
return this.onToken(this._toToken(this._tokensQueue.shift()));
}
if (!this.hasMoreTokens()) {
return this.onToken(EOF_TOKEN);
}
let string = this._string.slice(this._cursor);
let lexRulesForState = lexRulesByConditions[this.getCurrentState()];
for (let i = 0; i < lexRulesForState.length; i++) {
let lexRuleIndex = lexRulesForState[i];
let lexRule = lexRules[lexRuleIndex];
let matched = this._match(string, lexRule[0]);
// Manual handling of EOF token (the end of string). Return it
// as `EOF` symbol.
if (string === '' && matched === '') {
this._cursor++;
}
if (matched !== null) {
yytext = matched;
yyleng = yytext.length;
let token = lexRule[1].call(this);
if (!token) {
return this.getNextToken();
}
// If multiple tokens are returned, save them to return
// on next `getNextToken` call.
if (Array.isArray(token)) {
const tokensToQueue = token.slice(1);
token = token[0];
if (tokensToQueue.length > 0) {
this._tokensQueue.unshift(...tokensToQueue);
}
}
return this.onToken(this._toToken(token, yytext));
}
}
if (this.isEOF()) {
this._cursor++;
return EOF_TOKEN;
}
this.throwUnexpectedToken(
string[0],
this._currentLine,
this._currentColumn
);
},
/**
* Throws default "Unexpected token" exception, showing the actual
* line from the source, pointing with the ^ marker to the bad token.
* In addition, shows `line:column` location.
*/
throwUnexpectedToken(symbol, line, column) {
const lineSource = this._string.split('\n')[line - 1];
let lineData = '';
if (lineSource) {
const pad = ' '.repeat(column);
lineData = '\n\n' + lineSource + '\n' + pad + '^\n';
}
throw new SyntaxError(
`${lineData}Unexpected token: "${symbol}" ` +
`at ${line}:${column}.`
);
},
getCursor() {
return this._cursor;
},
getCurrentLine() {
return this._currentLine;
},
getCurrentColumn() {
return this._currentColumn;
},
_captureLocation(matched) {
const nlRe = /\n/g;
// Absolute offsets.
this._tokenStartOffset = this._cursor;
// Line-based locations, start.
this._tokenStartLine = this._currentLine;
this._tokenStartColumn =
this._tokenStartOffset - this._currentLineBeginOffset;
// Extract `\n` in the matched token.
let nlMatch;
while ((nlMatch = nlRe.exec(matched)) !== null) {
this._currentLine++;
this._currentLineBeginOffset = this._tokenStartOffset + nlMatch.index + 1;
}
this._tokenEndOffset = this._cursor + matched.length;
// Line-based locations, end.
this._tokenEndLine = this._currentLine;
this._tokenEndColumn = this._currentColumn =
(this._tokenEndOffset - this._currentLineBeginOffset);
},
_toToken(tokenType, yytext = '') {
return {
// Basic data.
type: tokenType,
value: yytext,
// Location data.
startOffset: this._tokenStartOffset,
endOffset: this._tokenEndOffset,
startLine: this._tokenStartLine,
endLine: this._tokenEndLine,
startColumn: this._tokenStartColumn,
endColumn: this._tokenEndColumn,
};
},
isEOF() {
return this._cursor === this._string.length;
},
hasMoreTokens() {
return this._cursor <= this._string.length;
},
_match(string, regexp) {
let matched = string.match(regexp);
if (matched) {
// Handle `\n` in the matched token to track line numbers.
this._captureLocation(matched[0]);
this._cursor += matched[0].length;
return matched[0];
}
return null;
},
/**
* Allows analyzing, and transforming token. Default implementation
* just passes the token through.
*/
onToken(token) {
return token;
},
};