@nicholasdly/subscript

Evaluate arithmetic, unit conversions, and time zones from natural language — zero dependencies or network requests.

1.8034 m
{ "ok": true, "value": { "value": 1.8034, "unit": { "id": "metre", "symbol": "m" } }, "text": "1.8034 m" }

Installation

Usage

Evaluate natural language queries anywhere in your project using the default configured evaluate function.

import { evaluate } from "@nicholasdly/subscript";

evaluate("20 c to f");
// { ok: true, text: "68 °F", ... }

evaluate("1 m in ft");
// { ok: true, text: "3.28084 ft", ... }

evaluate("(2 + 3) * 4 km in miles");
// { ok: true, text: "12.4274 mi", ... }

Configuration

Use createSubscript to configure the clock, locale, and format of the parser.

import { createSubscript, isZonedTime } from "@nicholasdly/subscript";

const subscript = createSubscript({
  now: () => ({ epochMilliseconds: Date.UTC(2026, 0, 15, 18, 0, 0) }),
});

const time = subscript.evaluate("3pm PST in Tokyo");
// time.text === "8:00 AM JST, Jan 16"
// isZonedTime(time.value) === true

Spans

spans returns highlight ranges for the original string. It does not evaluate.

createSubscript().spans("20 c to f");
// [
//   { start: 0, end: 2, kind: "number" },
//   { start: 3, end: 4, kind: "unit" },
//   { start: 5, end: 7, kind: "converter" },
//   { start: 8, end: 9, kind: "unit" },
// ]

Results

The result of an evaluation is a Result object, representing either a successful answer or a typed failure. Nothing in the API throws for bad input — you check ok and then read value or reason .

type Result =
  | {
      ok: true;
      value: Quantity | ZonedTime;
      text: string;
      alternates?: Alternate[];
    }
  | { ok: false; reason: Failure };