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

infix

v1.3.1

Published

An implementation of infix notation for custom number types in JavaScript

Downloads

4

Readme

Installation

npm install infix

However, infix is not really useful on its own and should be coupled with a number provider.

Motivation

Since JavaScript offers no operator overloading support, it can be cumbersome to work with number types other than the built-in number. Common custom number types include bignums and rationals. This library attempts to make it more convenient, and more uniform, to work with different number types.

Typically, custom number type usage can look like this:

bignum.add(5, bignum.mul(bignum.div(10, 2), x))

Using infix, the above could end up looking like this:

evaluate("5 + 10/2 * $0", x)

The infix version is more analogous to the way we normally work with calculations.

Usage

infix needs to work with a specific user defined number type. To help you get up and running, the library includes a number provider backed by the native number type: nativeNumberProvider. With this, we can evaluate some expressions:

var infix = require('infix');

console.log(infix.evaluate("10/2", infix.nativeNumberProvider));
// `5` is logged

If you want to evaluate multiple different things, it makes sense to specify the number provider once:

var evaluate = infix.evaluatorFor(infix.nativeNumberProvider);
console.log(evaluate("10*2"), evaluate("10/2"));
// `20 5` is logged

infix supports placeholders in the expression, which will be replaced by arguments. Placeholders are composed of a $-symbol and an integer:

console.log(evaluate("10*$0+$1", 1, 5));
// `15` is logged

The placeholders can be in any order. Their corresponding argument will be matched by the number, so $0 will match the first number argument, $1 the next, and so on. It is permitted to skip indexes in the placeholders, using for example only numbers $3 and $4, which would mean arguments 0, 1 and 2 would be ignored.

When evaluating the same expression multiple times, it can be a drag on execution speed to parse the expression every time. infix offers support for compiling a given expression to a function:

var f = infix.compile("10*$0+$1", infix.nativeNumberProvider);
console.log(f(2, -3), f(10, 2));
// `17 102` is logged

Similarly to evaluatorFor above, infix also offers compilerFor:

var compile = infix.compilerFor(infix.nativeNumberProvider);
var g = compile("10*$0+$1");
var h = compile("$1/$0+$0");
console.log(g(2, -3), h(2, 10));
// `17 7` is logged

The compilation process will also give the chosen number provider a chance to parse and cache all the constants needed in the expression up front.

Compiling the expression ahead of time can be an impediment to readability. A compromise between the two strategies is the memoizing compiler, which compiles any expressions and remembers the result for reuse later:

var compile = infix.memoizing.compilerFor(infix.nativeNumberProvider);

for (var i = 0; i < 100; i++) {
    console.log(compile("100 - $0")(i));
}
// The numbers 100..1 are logged in descending order

The memoizing comes with a small constant time overhead. As in any optimization situation, choosing the most efficient strategy is a matter of measuring in a real scenario. However, the following rules of thumb hold true:

  • For one-off expressions, the non-memoizing evaluator is the most efficient
  • The greater the complexity of the custom number provider, the smaller the difference between compilerFor, memoizing.compilerFor and evaluatorFor
  • The greater the complexity of the given expression, the smaller the difference between compilerFor and memoizing.compilerFor
  • The greater the complexity of the given expression, the greater the difference between any compiled version and evaluatorFor

For even more eager compilation, it is possible to generate the JavaScript source code for the generated functions by using infix.codegen:

console.log(infix.codegen("10*$0+$1"));

The output from this is compact, but we can prettify it and add comments for explanation:

// `np` below is the number provider
(function (np) {
    // The constants in the expression are parsed and the results cached:
    var c0 = np.parseInt('10');

    // Given this closure, the generated function will work as required:
    return function ($0, $1) {
        return np['+'](np['*'](c0, $0), $1);
    };
})

The output from codegen can be used for compiling the expression ahead of time, saving execution time in a deploy scenario. When compiling everything ahead of time, the infix library is no longer needed for deploy, so this can also be a win for deploy asset size.

Using with untrusted input

infix can also be used for evaluating expressions in user input. In this context it can be confusing to allow the placeholders. If you want to offer support for evaluating simple arithmetic expressions, use the evaluator that rejects references:

var evaluate = infix.noReferences.evaluatorFor(infix.nativeNumberProvider);

console.log(evaluate("10/2"));
// `5` is logged

console.log(evaluate("10/$0", 2));
// An exception is thrown

Implementing a number provider

All of this is not very valuable until you supply another number provider. Fortunately, the interface required for a number provider is fairly simple:

{
    parseInt: function (literal) {
        // return an acceptable object corresponding to the value in the
        // string `literal`. `literal` matches /^-?[0-9]+$/

        // nativeNumberProvider implements this like so:
        return parseInt(x, 10);
    },
    parseDecimal: function (before, after) {
        // return an acceptable object corresponding to the decimal value
        // that has the string `before` before the decimal point and the
        // string `after` after the decimal point.

        // For example the input string `3.14` would generate a call to
        // parseDecimal with `before` as "3" and `after` as "14".

        // `before` matches /^-?[0-9]+$/
        // `after` matches /^[0-9]+$/

        // nativeNumberProvider implements this like so:
        return parseFloat(before + "." + after);
    },
    // Then follows four operators. They each take two arguments, the first
    // for the left hand side of the operator and the second for the right
    // hand side. The values passed in are either from one of the parse
    // functions above, a result of one of the operator functions below, or
    // an argument passed in for a placeholder in the compiled function.

    // nativeNumberProvider implements the operators like so:
    "+": function (lhs, rhs) { return lhs + rhs; },
    "-": function (lhs, rhs) { return lhs - rhs; },
    "*": function (lhs, rhs) { return lhs * rhs; },
    "/": function (lhs, rhs) { return lhs / rhs; },
}