src/Parser/Parser.js
'use strict';
import { Lexer, Token } from '../Lexer';
/**
* The botlang parser for creating the abstract syntax tree.
*/
class Parser {
/**
* Create a Parser.
* @param {Lexer} lexer
* @throws {TypeError}
*/
constructor(lexer) {
if (!(lexer instanceof Lexer)) {
throw new TypeError('Argument "lexer" must be an instance of type "Lexer".');
}
/**
* @private
* @type {Lexer}
*/
this.lexer = lexer;
/**
* @private
* @type {Array}
*/
this.syntaxTree = [];
/**
* @private
* @type {Integer}
*/
this.syntaxTreeIndex = 0;
}
/**
* @private
* @return {Object|null}
*/
getLastNode() {
return 'undefined' !== typeof this.syntaxTree[this.syntaxTreeIndex - 1]
? this.syntaxTree[this.syntaxTreeIndex - 1]
: null;
}
/**
* @private
* @param {Token} token
* @return {Boolean}
*/
static isResponse(token) {
return token instanceof Token && 'operation' === token.getType() && '-' === token.getValue();
}
/**
* @private
* @param {Token} token
* @return {Boolean}
*/
static isTrigger(token) {
return token instanceof Token && 'operation' === token.getType() && '+' === token.getValue();
}
/**
* Parse input and return abstract syntax tree (AST)
* @return {Object}
*/
parse() {
while (!this.lexer.eof()) {
const token = this.lexer.next();
if (!(token instanceof Token)) {
continue;
}
if (Parser.isTrigger(token)) {
this.pushNodeToSyntaxTree(this.parseTrigger());
}
if (Parser.isResponse(token) && 'trigger' === this.getLastNode().type) {
this.getLastNode().responses.push(this.parseResponse());
}
}
return {
type : 'program',
body : this.syntaxTree
};
}
/**
* @private
* @return {Object}
*/
parseTrigger() {
const trigger = this.lexer.next();
let pattern = '';
if ('string' !== trigger.getType()) {
return this.lexer.inputError('Expected trigger pattern after trigger identifier.');
}
pattern = Parser.replaceWildcard(trigger.getValue());
pattern = Parser.replaceStringSubstitution(pattern);
return {
type : 'trigger',
src : trigger.getValue(),
pattern,
responses : []
};
}
/**
* @private
* @return {Object}
*/
parseResponse() {
const response = this.lexer.next();
if ('string' !== response.getType()) {
this.lexer.inputError('Expected string after response identifier.');
}
return {
type : 'response',
value : response.getValue()
};
}
/**
* @private
* @param {Object} node
* @return {void}
*/
pushNodeToSyntaxTree(node) {
this.syntaxTreeIndex += 1;
this.syntaxTree.push(node);
}
/**
* Replace string substitution [$] character
* @param {String} string
* @return {String}
*/
static replaceStringSubstitution(string) {
return string.replace(/(\s?)\$(\s?)/g, '\\s?(\\w+?)\\s?');
}
/**
* Replace wildcard [*] character
* @param {String} string
* @return {String}
*/
static replaceWildcard(string) {
return string.replace(/(\s?)\*(\s?)/g, '(\\w?)');
}
}
export default Parser;