npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

blu_parser

v1.1.4

Published

blu_parser

Readme

Blu's parser implementation

A lightweight basic recursive descend parser that natively do all_sat and supports both styles of recursion

Github

The source code is open sourced and available on this Repo

Usage

Install

npm i blu_parser

Import

The parser is available from the package's file

import { Parser } from "blu_parser"

Benchmark

One can run the default benchmark script on the number ruleset:

npm run benchmark

Or import the benchmark function and run it on your own ruleset:

import { bench } from "blu_parser"
bench(
    iter,
    unparse_depth,
    timeout,
    option,
    parser?
)

Create a parser

Parser supports the typical fragments, rules and also groups.

Example:

const parser = new Parser()
    .group("digit", ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] as const)
    .rule("digits_recur", ["digit", "digits"] as const, ["digits"])
    .rule("digit_single", ["digit"] as const, ["digits"])
  • Rules are contains a sequence of fragements and/or groups and/or literal tokens
  • Fragments are reusable sequences of literal tokens or group names that can be used in rules but do not participate in matches
  • Groups (NEW) are sets of strings that matches any one of the literal tokens and/or rule names names in the set

Group CAN contain other groups except the special default ones

Parser DO NOT SUPPORT regex and lookahead/lookbehind assertions, however it does supports the following features:

  • Left and right recursion
  • Ambiguous grammars
  • All-sat parsing (returns all possible matches instead of just one)

Parser however, do not support traditional rule construction through OR() or MANY(), instead you HAVE to manually list out all possible combinations of paths.

Example:

const parser = new Parser()
    .rule("optAB2", ["a", "b"] as const, ["opt_ab"])
    .rule("optAB1", ["b"] as const,      ["opt_ab"])
const parser = new Parser()
    .rule("a_or_b1", ["a"] as const, ["a_or_b"])
    .rule("a_or_b2", ["b"] as const, ["a_or_b"])
const parser = new Parser()
    .rule("many1", ["a", "a_many"] as const, ["many"])
    .rule("many2", ["a"] as const,           ["many"])

This is a design choice to keep the parser simple and straightforward, and also to allow for more flexibility in the grammar design. This hopefully supports all typical grammars.

Note that as const is required to let the parser infer the literal token type, which is used for rule checking purposes. Missing as const make ts infers the array as string[], which is not helpful and will cause type errors in rule construction.

Special groups

There are 2 default groups that can be used without declaration, which are T and T

  • T matches any single token
const parser = new Parser()
    .rule("assign_stmt", ["T", "=", "T"] as const)
  • T+ matches any sequence of tokens with length of 1 or more, ungreedy until the next "anchor" is macthed
const parser = new Parser()
    .rule("assign_many", ["T+", "=", "T"] as const)

Type restriction

The parser's contruction methods is backed by typescript's type inference that should disallow invalid grammar construction. For example, the following code should throw a type error because rule is empty

const parser = new Parser()
    .rule("empty_rule", [] as const, ["empty_rule"])

also this for overlapping names

const parser = new Parser()
    .rule("a_rule", ["a"] as const, ["a_rule"])
    .rule("a_rule", ["b"] as const, ["a_rule"])

Parsing

The parser's parse() method takes in an input string and returns an array of all possible matches via the RuleMatch object

Example:

const parser = new Parser()
    .rule("a_rule", ["a"] as const, ["a_rule"])

const [result, log] = parser.parse("a", { token_name_key : undefined, tokenizer "char" }) // RuleMatch[], LogReport[]
console.log(result) // RuleMatch[]
console.log(log) // LogReport[]

The log returned from the parse() method is a LogReport object that contains the parsing log, which can be used for debugging and analysis purposes, also contain partial matches that can be used for incremental parsing.

The Parser Option object passed into the parse() method allows you to specify parsing options such as tokenization method and token name access method, which will be explained in the next section.

Parse Option - Tokenization

The parser defaultly support character based and word based tokenization, but works with any tokenizer that provides a token name.

One can specify token name access in the Parser Option object upon a parse() call, for example:

const parser = new Parser()
    .rule("a_rule", ["a"] as const, ["a_rule"])

const result = parser.parse("a", { 
    token_name_key : "type", 
    tokenizer : {
        tokenize(input: string) {
            return {
                type : //type here
            }
        }
    } 
})

Parse option - Parsing

Asside from tokenization options, the Parser Option object also allows you to specify some options for the parsing process, such as:

/** 
 * Depth of how much of the queue should be exhausted before unsat, 
 * 
 * ~ Roughly how many rules is applied 
 * 
 * Set to **Infinity** to ignore
 * 
 * Default is **10**
 * */
recurse_depth? : number,

/** 
 * By default, the engine prioritizes rules based on *How much anchors* is matched
 * 
 * Set this to a positive number to *relax* this condition so rules even with lesser anchor cound still matched
 * 
 * Around 3 - 4 is reasonable, since the heuristic is not that smart
 *  
 * Set to **0** to only return the very best rule
 * 
 * Set to **-Infinity** to ignore
 *  
 * Default is 4 ( so rule even with anchor count 4 less than the max is still retained )
 */
heuristic_filter_relaxing? : number,

/**
 * By default, matches with the same segmentation is ignored
 * 
 * set to true to still keep those matches, which may be useful for debugging or if different rule with the same segmenation is wanted
 * */
keep_duplicate_match? : boolean, 

Post processing

The parser supports adding a post processing function to any rule, which automatically infers the input type of the post processor function based on the rule definition and the current state of the parser.

The parser supports 2 ways to add post procssor:

Example 1:

const parser = new Parser()
    .rule("a_rule", ["a"] as const, [])
    .rule("b_rule", ["a_rule"] as const, [])
    .addPostProcess("a_rule", (...match) => {
        //match is inferred as [TokenMatch] since a_rule is defined as ["a"] and "a" is a literal token
        return match.raw.length //sets a_rule's returns a number
    })
    .addPostProcess("b_rule", (...match) => {
        //match is inferred as [number], 
        // because the post processor of "a_rule" returns number and "b_rule" is defined as ["a_rule"]
        return match.raw.length //sets b_rule's returns a number
    })

or Example 2:

const parser = new Parser()
    .rule("one", ["1"], [], () => 1) //sets one to return number 1
    .rule("two", ["one"], [], (one) => one + 1) //sets two to return one + 1, which is 2

Unparsing

unparse is a functionality borrowed from Nearley.js that generates a random sequence of tokens that matches the grammar, which can be used for testing and benchmarking purposes

const parser = new Parser()
    .rule("a_rule", ["a"] as const, ["a_rule"])

const input = parser.unparse(max_depth?)

Benchmark results

The parser is benchmarked on a simple number/float ruleset:

========== BENCHMARK RESULTS ==========
Total iterations requested: 1000
Successful runs: 1000
Timed out: 0
Max unparse depth: 10

Timing metrics:
  Min:     0.90260000 ms
  Max:     16.31010000 ms
  Average: 2.61547250 ms
  Median:  2.07500000 ms
  Std Dev: 1.83735047 ms

Percentiles:
  P95: 5.85370000 ms
  P99: 9.79470000 ms

Throughput Per Run: 382.34 parses/second
Average Tokens Per Run: 6.53
Throughput Per Token: 2495.15 tokens/second
Total time: 2615.47 ms (2.6155 seconds)