Home Reference Source Repository

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