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 🙏

© 2024 – Pkg Stats / Ryan Hefner

eclog

v1.0.3

Published

A beautiful backtracking & generator library.

Downloads

3

Readme

eclog

A beautiful backtracking & generator library.

const suit = $(...'♠♦♣♥')
const num = $(...'A23456789', '10', ...'JQK')
const card = $(() => `${num()}${suit()}`) //  <-- magic happens here
console.log([...card].slice(0, 13).join(' '))
// A♠ A♦ A♣ A♥ 2♠ 2♦ 2♣ 2♥ 3♠ 3♦ 3♣ 3♥ 4♠

Use it to generate sentences

// from https://www-users.cs.umn.edu/~gini/prolog/dcg.html
const noun = $('cat', 'mouse')
const verb = $('scares', 'hates')
const determiner = $('the', 'a')
const verb_phrase = $(() => `${verb()} ${noun_phrase()}`)
const noun_phrase = $(() => `${determiner()} ${noun()}`)
const sentence = $(() => `${noun_phrase()} ${verb_phrase()}`)
console.log(...[...sentence].slice(0, 10))
/*
the cat scares the cat
the cat scares the mouse
the cat scares a cat
the cat scares a mouse
the cat hates the cat
the cat hates the mouse
the cat hates a cat
the cat hates a mouse
the mouse scares the cat
the mouse scares the mous
*/

Or to find solutions

// Sum of 4 numbers is 10
const num = $(1, 2, 3, 4, 5, 6, 7, 8, 9)
const threeNums = $(() => {
  const a = num()
  if (a >= 10) fail()
  const b = num()
  if (a + b >= 10) fail()
  const c = num()
  if (a + b + c >= 10) fail()
  return [a, b, c, 10 - a - b - c]
})
console.log(...[...threeNums].map(nums => nums.join('+')).slice(0, 10)))
/*
1+1+1+7
1+1+2+6
1+1+3+5
1+1+4+4
1+1+5+3
1+1+6+2
1+1+7+1
1+2+1+6
1+2+2+5
1+2+3+4
*/

Specification

The default export $ function creates an eclog. It accepts zero or more arguments, which are the branches. A branch is either a function (a builder function) or a raw value.

$(4) // same as:
$(() => 4)

The eclog is iterable. It iterates the solutions of each of its branches. If the branch is a raw value, it's the only solution.

const x = $(5, 6)
console.log([...x]) // [5, 6]

If the branch is a builder function, it will be called at least once. If it calls no eclogs, its return value is its only solution.

const x = $(
  () => 5,
  () => 6
)
console.log([...x]) // [5, 6]

The builder function is allowed to call eclogs as functions. In this case, the builder function might be called multiple times by the system. At each new call, the called eclogs will return a new set of values so that each combination

const name = $('Jane', 'John')
console.log([...$(() => `Hello, ${name()}`)]) // ['Hello, Jane!', 'Hello, John!']

The builder function must be a pure function in the sense that it must return the same value if the called eclogs return the same set of values (in this sense, return values of eclogs are considered the inputs of the builder function). However, it is allowed to call different eclogs depending on the values of previous eclogs:

const state = $('CA', 'TX')
const CAcity = $('Los Angeles', 'San Diego')
const TXcity = $('Houston', 'Dallas')
const city = $(() => {
  const s = state()
  const c = s == 'CA' ? CAcity() : TXcity()
  return `${c}, ${s}`
})
console.log([...city]) // ["Los Angeles, CA", "San Diego, CA", "Houston, TX", "Dallas, TX"]

The builder function is also allowed to create eclogs on the fly and call them immediately:

const numbers = $(() => $(10, 20)() + $(1, 2)())
console.log([...numbers]) // [11, 12, 21, 22]

The builder function is allowed to call an eclog several times:

const num = $(1, 2)
const pair = $(() => [num(), num()])
console.log([...pair]) // [[1, 1], [1, 2], [2, 1], [2, 2]]

If a called eclog has no solutions, it throws a special Failed exception, and the current run of the builder function stops immediately. The builder may be re-invoked if there is possibility of having other solutions by returning other values of called eclogs.

The fail export is an eclog with no solutions.

const num = $(1, 2, 3, 4, 5, 6, 7, 8, 9)
const divisorOf6 = $(() => {
  const d = num()
  if (6 % d != 0) fail()
  return d
})
console.log([...divisorOf6]) // [1, 2, 3, 6]

Calling an eclog with parameters results in those parameters being passed to its builder functions. It will immediately stop the running builder function with an exception.

const num = $(1, 2, 3, 4, 5, 6, 7, 8, 9)
const divisorOf = $(n => {
  const d = num()
  if (n % d != 0) fail()
  return d
})
const divisorOf9 = $(() => divisorOf(9))
console.log([...divisorOf9]) // [1, 3, 9]

The definition of fail is:

const fail = $()

Eclogs can also be called outside of a builder function: in this case they always return their first solution.

const num = $(9, 8, 7, 6, 5, 4, 3, 2, 1)
// The following is a so-called checker function: it returns its first argument or fails
const divisor = (a, b) => (b % a == 0 ? a : fail())
const divisorOf = $(n => divisor(num(), n))
// Another checker function
const eq = (a, b) => (a == b ? a : fail())
const gcd = $((a, b) => eq(divisorOf(a), divisorOf(b)))
console.log(gcd(9, 6)) // 3
console.log(gcd(8, 6)) // 2

Future plans

Store variables during the backtracking, e.g. for max depth or heuristics.

const depth = $(0)
const find = $(() => depth(depth() + 1))

Use a queue for breadth-first search.

const queue = $(1)
const find = $(() => {
  const c = queue()
  queue.add(2, 3)
})

Helper functions

range(1, 4)

License

MIT