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

@tokens-studio/unit-calculator

v0.0.5

Published

A tool to run calculations on strings with units

Readme

Unit Calculator

A tool to run calculations on strings with units.

The unit calculator lets you define custom rules on how mixed units will be resolved.

It will mainly be used to resolve math operations on design token epxressions.

Installation

npm i @tokens-studio/unit-calculator

Example

// Allows defining custom units
> 1h + 1min
[ "61min" ]

// Allows unit mixing defined per custom functions
> 10px + 1rem
[ "26px" ]

// Allows functions
> abs(2px)
[ "2px" ]

// Handles multiple expressions
> (2px * 4) - (2rem * 10) 1rem 2% * 10
[ "-312px", "1rem", "20%" ]

// Handles strings
> 10px - 1px solid green
[ "9px", "solid", "green" ]

Running it

In the console

npm run cli                           # Start a repl
npm run cli "1+1"                     # Evaluate expression
npm run cli --help                    # Show help

CLI Options

The CLI supports various configuration options:

# Control string handling
npm run cli --no-strings "1 + 2"
npm run cli --strings "hello world"

# Control multiple expressions
npm run cli --no-multiple-expressions "1 + 1 2 + 2"  # Will throw error
npm run cli --multiple-expressions "1 + 1 2 + 2"     # Returns [2, 4]

# Specify allowed units
npm run cli --units "px,em,rem" "1px + 2em"

# Combine options
npm run cli --no-strings --units "px,rem" "1px + 2rem"

CLI Help

npm run cli --help

Shows all available options:

  • --strings / --no-strings: Allow/disallow strings in expressions
  • --multiple-expressions / --no-multiple-expressions: Allow/disallow multiple expressions
  • --units <list>: Comma-separated list of allowed units
  • --help, -h: Show help message

Configuration

The configuration for the engine looks like this:

export interface CalcConfig {
  allowedUnits: Set<string>;
  mathFunctions: Record<string, args => number>;
  mathConstants: Record<string, number>;
  unitConversions: Map<string, (LeftToken, RightToken) => {value: number; unit: string | null}>;
}

allowedUnits

Define which units are allowed in your engine, per default CSS units are allowed.

Right now you can use any string value as unit to be allowed, any non allowed unit will throw.

For instance to allow measure units

{allowedUnits: new Set(["km", "m", "cm", "mm"])}

So you can use them in your calculations (once you've defined the conversion entries)

> 2km + 2km => [ "4km" ]

Undefined units will throw

> 2foo + 2bar
Invalid unit: "foo". Allowed units are: px, em, rem, %, vh, vw, vmin, vmax, cm, mm, in, pt, pc

unitConversions

You can define how units convert by passing in a Vector of rules.

These rules will define how the engine will convert units when you mix them.

Rules

  • Mixing the same units will always preserve units.
  • Unitless Numbers will be matched with null, e.g.: [null, '+', 'px']
  • You can give a wildcard operator with *, e.g.: ['*', '+', '%']

Defining a conversion table entry

To define an entry you give it a 3-tuple of [unit, operator, unit] and a function that handles the conversion.

For example if you want to convert rems when mixing with px by multiplying rem by a base size you could pass:

["px", "+", "rem"], (left, right) => {value: left.value + (right.value * 16), unit: "px"},

So you get this result

> 1px + 1rem
[ "17px" ]

Percent Example

You could define a unit % that will always add x% to the other value

export const createPercentConfig = function () {
  const addPercent = (percentToken, unitToken) => ({
    value: (unitToken.value / 100) * percentToken.value + unitToken.value,
    unit: unitToken.unit,
  });

  const config = createConfig();

  return addUnitConversions(config, [
    [
      ["%", "+", "*"], (left, right) => addPercent(left, right),
    ],
    [
      ["*", "+", "%"], (left, right) => addPercent(right, left),
    ],
  ]);
};

Now you can add percent to any value that you accept

> 100px + 10%
[ "110px" ]

mathFunctions

You can supply your own math functions via the mathFunctions property.

Per default all functions in js Math are included.

You could define custom functions like this

const options = {
    mathFunctions: { add: (a, b) => {value: a.value + b.value}, unit: a.unit },
};

Now you can use your custom functions like this

> add(10px, 10px)
[ "20px" ]

Unit mixing has to be custom handled in your function.

Concepts

This project demonstrates the fundamentals of a Pratt Parser. It's based on this repository, this paper by Vaughan Pratt, and also learns from this article and this article.

Parser

In general, the Pratt Parser solves the following problem: given the string "1 + 2 * 3", does the "2" associate with the "+" or the "*". It also solves "-" being both a prefix and infix operator, as well as elegantly handling right associativity.

The Pratt Parser is based on three computational units:

parser.expr(rbp)    // The expression parser
token.nud()         // "Null Denotation" (operates on no "left" context)
token.led(left, bp) // "Left Denotation" (operates with "left" context)

The parser.expr(rbp) function looks like:

function expr(rbp) {
  let left = lexer.next().nud()               // (1)
  while (rbp < lexer.peek().bp) {             // (2)
    const operator = lexer.next()           // (3)
    left = operator.led(left, operator.bp)  // (4)
  }
  return left
}

Of course, nud and led may recursively call expr.

The expr method can be summarized in english as "The loop (while) builds out the tree to the left, while recursion (led -> expr) builds the tree out to the right; nud handles prefix operators":

function expr(rbp) {
  // (1) handle prefix operator
  // (2) continue until I encounter an operator of lesser precedence than myself
  // (3) "eat" the operator
  // (4) give the operator the left side of the tree, and let it build the right side; this new tree is our new "left"
}

References