UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

229 lines (186 loc) 5.53 kB
/** * 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; }, };