lib/index.js
const createStop = () => {
let stopCalled = false;
return Object.assign((x) => {
stopCalled = true;
return x;
}, {
isStopped() {
return stopCalled;
},
});
};
/**
* A Pratt parser.
* @example
* const lex = new perplex.Lexer('1 + -2 * 3^4')
* .token('NUM', /\d+/)
* .token('+', /\+/)
* .token('-', /-/)
* .token('*', new RegExp('*'))
* .token('/', /\//)
* .token('^', /\^/)
* .token('(', /\(/)
* .token(')', /\)/)
* .token('$SKIP_WS', /\s+/)
*
* const parser = new Parser(lex)
* .builder()
* .nud('NUM', 100, t => parseInt(t.match))
* .nud('-', 10, (t, bp) => -parser.parse(bp))
* .nud('(', 10, (t, bp) => {
* const expr = parser.parse(bp)
* lex.expect(')')
* return expr
* })
* .bp(')', 0)
*
* .led('^', 20, (left, t, bp) => Math.pow(left, parser.parse(20 - 1)))
* .led('+', 30, (left, t, bp) => left + parser.parse(bp))
* .led('-', 30, (left, t, bp) => left - parser.parse(bp))
* .led('*', 40, (left, t, bp) => left * parser.parse(bp))
* .led('/', 40, (left, t, bp) => left / parser.parse(bp))
* .build()
* parser.parse()
* // => 161
*/
export class Parser {
/**
* Constructs a Parser instance
* @param {ILexer<T>} lexer The lexer to obtain tokens from
*/
constructor(lexer) {
/**
* The lexer that this parser is operating on.
* @type {ILexer<T>}
*/
this.lexer = lexer;
this._nuds = new Map();
this._leds = new Map();
this._bps = new Map();
}
_type(tokenOrType) {
return tokenOrType && typeof tokenOrType.isEof == 'function'
? tokenOrType.type
: tokenOrType;
}
/**
* Create a {@link ParserBuilder}
* @return {ParserBuilder<T>} Returns the ParserBuilder
*/
builder() {
return new ParserBuilder(this);
}
/**
* Define binding power for a token-type
* @param {IToken<T>|T} tokenOrType The token type to define the binding power for
* @returns {number} The binding power of the specified token type
*/
bp(tokenOrType) {
if (tokenOrType == null)
return Number.NEGATIVE_INFINITY;
if (tokenOrType &&
typeof tokenOrType.isEof == 'function' &&
tokenOrType.isEof())
return Number.NEGATIVE_INFINITY;
const type = this._type(tokenOrType);
return this._bps.has(type) ? this._bps.get(type) : Number.POSITIVE_INFINITY;
}
/**
* Computes the token's `nud` value and returns it
* @param {NudInfo<T>} info The info to compute the `nud` from
* @returns {any} The result of invoking the pertinent `nud` operator
*/
nud(info) {
const bp = this.bp(info.token);
let fn = this._nuds.get(info.token.type);
if (!fn) {
const { start } = info.token.strpos();
throw new Error(`Unexpected token: ${info.token.match} (at ${start.line}:${start.column})`);
}
return fn(info);
}
/**
* Computes a token's `led` value and returns it
* @param {LedInfo<T>} info The info to compute the `led` value for
* @returns {any} The result of invoking the pertinent `led` operator
*/
led(info) {
const bp = this.bp(info.token);
let fn = this._leds.get(info.token.type);
if (!fn) {
const { start } = info.token.strpos();
throw new Error(`Unexpected token: ${info.token.match} (at ${start.line}:${start.column})`);
}
return fn(info);
}
/**
* Kicks off the Pratt parser, and returns the result
* @param {ParseOpts<T>} opts The parse options
* @returns {any}
*/
parse(opts = { terminals: [0] }) {
const stop = createStop();
const check = () => {
if (stop.isStopped())
return false;
let t = this.lexer.peek();
const bp = this.bp(t);
return opts.terminals.reduce((canContinue, rbpOrType) => {
if (!canContinue)
return false;
if (typeof rbpOrType == 'number')
return rbpOrType < bp;
if (typeof rbpOrType == 'string')
return t.type != rbpOrType;
}, true);
};
const mkinfo = (token) => {
const bp = this.bp(token);
return { token, bp, stop, ctx: opts.ctx };
};
if (!opts.terminals)
opts.terminals = [0];
if (opts.terminals.length == 0)
opts.terminals.push(0);
let left = this.nud(mkinfo(this.lexer.next()));
while (check()) {
const operator = this.lexer.next();
left = this.led(Object.assign(mkinfo(operator), { left }));
}
return left;
}
}
/**
* Builds `led`/`nud` rules for a {@link Parser}
*/
export class ParserBuilder {
/**
* Constructs a ParserBuilder
* See also: {@link Parser.builder}
* @param {Parser<T>} parser The parser
*/
constructor(parser) {
this._parser = parser;
}
/**
* Define `nud` for a token type
* @param {T} tokenType The token type
* @param {number} bp The binding power
* @param {NudFunction<T>} fn The function that will parse the token
* @return {ParserBuilder<T>} Returns this ParserBuilder
*/
nud(tokenType, bp, fn) {
this._parser._nuds.set(tokenType, fn);
this.bp(tokenType, bp);
return this;
}
/**
* Define `led` for a token type
* @param {T} tokenType The token type
* @param {number} bp The binding power
* @param {LedFunction<T>} fn The function that will parse the token
* @return {ParserBuilder<T>} Returns this ParserBuilder
*/
led(tokenType, bp, fn) {
this._parser._leds.set(tokenType, fn);
this.bp(tokenType, bp);
return this;
}
/**
* Define both `led` and `nud` for a token type at once.
* The supplied `LedFunction` may be called with a null `left`
* parameter when invoked from a `nud` context.
* @param {strTng} tokenType The token type
* @param {number} bp The binding power
* @param {LedFunction<T>} fn The function that will parse the token
* @return {ParserBuilder<T>} Returns this ParserBuilder
*/
either(tokenType, bp, fn) {
return this.nud(tokenType, bp, ({ token, bp, stop, ctx }) => fn({ left: null, token, bp, stop, ctx })).led(tokenType, bp, fn);
}
/**
* Define the binding power for a token type
* @param {T} tokenType The token type
* @param {number} bp The binding power
* @return {ParserBuilder<T>} Returns this ParserBuilder
*/
bp(tokenType, bp) {
this._parser._bps.set(tokenType, bp);
return this;
}
/**
* Returns the parent {@link Parser} instance
* @returns {Parser<T>}
*/
build() {
return this._parser;
}
}
export default Parser;
//# sourceMappingURL=index.js.map