Parse
Turn text into a canonical, validated value.
Prefer the live demos? Open this section in the interactive docs.
Turn any text into a canonical, validated value, then read it back. Use lingo() for flexible fields; use shaped helpers for fixed contracts.
import { lingo, parseQuantity, parseRange } from "@pascal-app/lingo"
const height = parseQuantity("5'11\"", { kind: "length" })
if (height.ok) {
height.quantity.to("m").value
height.quantity.format({ compound: ["ft", "in"] })
}
const any = lingo("72 in to cm")
const range = parseRange("between 5 and 10 kg", { kind: "mass" })Captions:
- Universal parse: Conversion requests return target units without losing original spans.
- Parse readout: Warnings can succeed; only errors block the value.
- System and number format variants: System picks the gallon family; numberFormat resolves separator ambiguity.
Canonical examples from the public docs:
2 ftparses as a length quantity with base0.6096 m.5'11"parses as approximately1.8034 mwith feet/inches parts.72 in to cmparses as a conversion with converted value182.88 cm.between 5 and 10 kgparses as a mass range.a few minutesparses as an approximate duration range.it's hotparses as a temperature fuzzy range whenkind: "temperature"is supplied.
Autocomplete anything
completions(text, opts?) from @pascal-app/lingo/complete returns ranked, fully-parsed canonical readings of partial or ambiguous input — unit-prefix fan-out, unit-ambiguity forks, number alternatives, implied units, and range tails — each a canonical text plus a successful quantity/range/conversion result. A completion is none of the other three: it is not a failure candidate, a success alternative, or an issue suggestion.
import { completions } from "@pascal-app/lingo/complete"
import { parseDate, parseDateRange } from "@pascal-app/lingo/date"
import { lingoInput } from "@pascal-app/lingo/dom"
import { useLingoInput } from "@pascal-app/lingo/react"
// Debounce in your UI (~120–150ms) — completions() re-parses on every call
const items = completions("10 kg to 16", { kind: "mass", limit: 8 })
// range tails fan out: 10–16 kg, 10–16 lb, …
completions("10", { units: ["kg", "lb", "m", "ft"] }) // optimistic without kind
// Inject the date engine (kept out of ./complete for size) — covers
// "noon tomorrow", "next month", and "3 days starting tomorrow"
const withDates = completions("noon tomorrow", {
date: (text) => {
const now = new Date()
const single = parseDate(text, { now })
return single.ok ? single : parseDateRange(text, { now })
},
})
lingoInput(input, {
kind: "mass",
complete: (text) => completions(text, { kind: "mass", limit: 8 }),
onComplete: (list) => renderGroupedDropdown(list),
})
// React exposes the same injected list plus headless highlight/selection state
const field = useLingoInput({
kind: "mass",
listboxId: "mass-options",
complete: (text) => completions(text, { kind: "mass", limit: 8 }),
})
field.completions
field.setHighlightedIndex(1)
field.selectCompletion()Inject it into a field with lingoInput({ complete, onComplete }) or useLingoInput({ complete }); the engine is never bundled into @pascal-app/lingo/dom or /react, and no popup UI ships. Pass date to opt into date completions without pulling @pascal-app/lingo/date into /complete.
Find values in text
findQuantities(text, opts?) scans free text and returns every quantity, range, and conversion it finds, each with a span pointing at the exact characters in the original string.
import { findQuantities } from "@pascal-app/lingo"
const found = findQuantities("ship 2 boxes at 5 kg each by friday")
// [{ result, span: { start, end } }, ...] — offsets into the original text
for (const { result, span } of found) {
if (result.type === "quantity") highlight(span, result.quantity)
}