@supercmd/calculator 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +193 -6
- package/dist/index.mjs +193 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1870,7 +1870,10 @@ var DATE_PATTERNS = [
|
|
|
1870
1870
|
/^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/i,
|
|
1871
1871
|
/^(?:(?:what(?:'s|s| is))\s+today|(?:what(?:'s|s| is))\s+today'?s date|what day is it|what date is .+|todays date|today'?s date|date today|current date|what day is .+|when is .+)$/i,
|
|
1872
1872
|
// Days between dates
|
|
1873
|
-
/^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i
|
|
1873
|
+
/^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i,
|
|
1874
|
+
// Days until
|
|
1875
|
+
/^(?:days?\s+)?(?:until|till|to)\s+.+$/i,
|
|
1876
|
+
/^(?:how many days?\s+)?(?:until|till|to|before)\s+.+$/i
|
|
1874
1877
|
];
|
|
1875
1878
|
function tryDateIntent(input) {
|
|
1876
1879
|
for (const pattern of DATE_PATTERNS) {
|
|
@@ -1880,12 +1883,31 @@ function tryDateIntent(input) {
|
|
|
1880
1883
|
}
|
|
1881
1884
|
return null;
|
|
1882
1885
|
}
|
|
1883
|
-
var CONVERSION_PATTERN = /^([\d.,]+)?\s*([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)$/i;
|
|
1886
|
+
var CONVERSION_PATTERN = /^([\d.,]+[kKmMbB]?)?\s*([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)$/i;
|
|
1887
|
+
function parseAmountWithSuffix(str) {
|
|
1888
|
+
if (!str) return 1;
|
|
1889
|
+
const m = str.match(/^([\d.,]+)([kKmMbB]?)$/);
|
|
1890
|
+
if (!m) return parseFloat(str) || 1;
|
|
1891
|
+
let numStr = m[1];
|
|
1892
|
+
const suffix = m[2].toLowerCase();
|
|
1893
|
+
if (numStr.includes(",")) {
|
|
1894
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
1895
|
+
const afterComma = numStr.slice(lastComma + 1);
|
|
1896
|
+
if (afterComma.length <= 2 && !numStr.includes(".")) {
|
|
1897
|
+
numStr = numStr.slice(0, lastComma) + "." + afterComma;
|
|
1898
|
+
} else {
|
|
1899
|
+
numStr = numStr.replace(/,/g, "");
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
const base = parseFloat(numStr);
|
|
1903
|
+
const multipliers = { k: 1e3, m: 1e6, b: 1e9 };
|
|
1904
|
+
return isNaN(base) ? 1 : base * (multipliers[suffix] ?? 1);
|
|
1905
|
+
}
|
|
1884
1906
|
function tryCurrencyOrCryptoIntent(input) {
|
|
1885
1907
|
const normalized = normalizeConversionInput(input);
|
|
1886
1908
|
const match = normalized.match(CONVERSION_PATTERN);
|
|
1887
1909
|
if (!match) return null;
|
|
1888
|
-
const amount =
|
|
1910
|
+
const amount = parseAmountWithSuffix(match[1]);
|
|
1889
1911
|
const fromToken = match[2].trim();
|
|
1890
1912
|
const toToken = match[3].trim();
|
|
1891
1913
|
const fromCrypto = resolveCrypto(fromToken);
|
|
@@ -1903,13 +1925,13 @@ function tryCurrencyOrCryptoIntent(input) {
|
|
|
1903
1925
|
}
|
|
1904
1926
|
return null;
|
|
1905
1927
|
}
|
|
1906
|
-
var UNIT_PATTERN = /^(-?[\d.,]+)\s*([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)$/i;
|
|
1907
|
-
var UNIT_PATTERN_NO_SPACE = /^(-?[\d.,]+)([a-zA-Z°]+)\s+(?:to|in|into|as)\s+([a-zA-Z°]+(?:\s+[a-zA-Z]+)?)$/i;
|
|
1928
|
+
var UNIT_PATTERN = /^(-?[\d.,]+[kKmMbB]?)\s*([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)$/i;
|
|
1929
|
+
var UNIT_PATTERN_NO_SPACE = /^(-?[\d.,]+[kKmMbB]?)([a-zA-Z°]+)\s+(?:to|in|into|as)\s+([a-zA-Z°]+(?:\s+[a-zA-Z]+)?)$/i;
|
|
1908
1930
|
function tryUnitIntent(input) {
|
|
1909
1931
|
const normalized = normalizeConversionInput(input);
|
|
1910
1932
|
const match = normalized.match(UNIT_PATTERN) || normalized.match(UNIT_PATTERN_NO_SPACE);
|
|
1911
1933
|
if (!match) return null;
|
|
1912
|
-
const amount =
|
|
1934
|
+
const amount = parseAmountWithSuffix(match[1]);
|
|
1913
1935
|
const fromToken = match[2].trim().toLowerCase();
|
|
1914
1936
|
const toToken = match[3].trim().toLowerCase();
|
|
1915
1937
|
const fromUnit = lookupUnit(fromToken);
|
|
@@ -2196,6 +2218,16 @@ var Parser = class {
|
|
|
2196
2218
|
if (this.pos < this.expr.length && /[+-]/.test(this.expr[this.pos])) this.pos++;
|
|
2197
2219
|
while (this.pos < this.expr.length && /[0-9]/.test(this.expr[this.pos])) this.pos++;
|
|
2198
2220
|
}
|
|
2221
|
+
const numEnd = this.pos;
|
|
2222
|
+
if (/[kKmMbB]/.test(this.charAt(this.pos)) && !/[a-zA-Z0-9_]/.test(this.charAt(this.pos + 1))) {
|
|
2223
|
+
const suffix = this.expr[this.pos].toLowerCase();
|
|
2224
|
+
this.pos++;
|
|
2225
|
+
const multipliers = { k: 1e3, m: 1e6, b: 1e9 };
|
|
2226
|
+
const numStr2 = this.expr.slice(start, numEnd);
|
|
2227
|
+
const num2 = parseFloat(numStr2);
|
|
2228
|
+
if (isNaN(num2)) throw new Error(`Invalid number: '${numStr2}'`);
|
|
2229
|
+
return num2 * multipliers[suffix];
|
|
2230
|
+
}
|
|
2199
2231
|
const numStr = this.expr.slice(start, this.pos);
|
|
2200
2232
|
const num = parseFloat(numStr);
|
|
2201
2233
|
if (isNaN(num)) throw new Error(`Invalid number: '${numStr}'`);
|
|
@@ -2300,6 +2332,25 @@ function evaluateMath(expression, locale = "en-US", precision = 10) {
|
|
|
2300
2332
|
};
|
|
2301
2333
|
}
|
|
2302
2334
|
}
|
|
2335
|
+
function normalizeEuropeanDecimalSeparators(expr) {
|
|
2336
|
+
let result = "";
|
|
2337
|
+
let depth = 0;
|
|
2338
|
+
for (let i = 0; i < expr.length; i++) {
|
|
2339
|
+
const ch = expr[i];
|
|
2340
|
+
if (ch === "(") depth++;
|
|
2341
|
+
else if (ch === ")") depth--;
|
|
2342
|
+
else if (ch === "," && depth === 0) {
|
|
2343
|
+
const prevIsDigit = i > 0 && /[0-9]/.test(expr[i - 1]);
|
|
2344
|
+
const nextIsDigit = i < expr.length - 1 && /[0-9]/.test(expr[i + 1]);
|
|
2345
|
+
if (prevIsDigit && nextIsDigit) {
|
|
2346
|
+
result += ".";
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
result += ch;
|
|
2351
|
+
}
|
|
2352
|
+
return result;
|
|
2353
|
+
}
|
|
2303
2354
|
function normalizeMathExpression(expression) {
|
|
2304
2355
|
let normalized = expression.trim().replace(/\s+/g, " ");
|
|
2305
2356
|
normalized = normalized.replace(/^(?:(?:what(?:'s|s| is))\s+the\s+|(?:what(?:'s|s| is))\s+|calculate\s+)/i, "");
|
|
@@ -2333,6 +2384,7 @@ function normalizeMathExpression(expression) {
|
|
|
2333
2384
|
}
|
|
2334
2385
|
}
|
|
2335
2386
|
normalized = normalized.replace(/\bdivided by\b/gi, "/").replace(/\btimes\b/gi, "*").replace(/\bplus\b/gi, "+").replace(/\bminus\b/gi, "-");
|
|
2387
|
+
normalized = normalizeEuropeanDecimalSeparators(normalized);
|
|
2336
2388
|
return normalized.replace(/\s+/g, " ").trim();
|
|
2337
2389
|
}
|
|
2338
2390
|
|
|
@@ -2643,6 +2695,25 @@ function evaluateDate(query) {
|
|
|
2643
2695
|
return formatDateResult(query, d, { iso: d.toISOString(), unix: Math.floor(d.getTime() / 1e3) });
|
|
2644
2696
|
}
|
|
2645
2697
|
}
|
|
2698
|
+
const daysUntilMatch = lower.match(/^(?:(?:how many )?days?\s+)?(?:until|till|to|before)\s+(.+)$/);
|
|
2699
|
+
if (daysUntilMatch) {
|
|
2700
|
+
const target = parseDateTarget(daysUntilMatch[1].trim());
|
|
2701
|
+
if (target) {
|
|
2702
|
+
const now = /* @__PURE__ */ new Date();
|
|
2703
|
+
now.setHours(0, 0, 0, 0);
|
|
2704
|
+
const targetDate = new Date(target);
|
|
2705
|
+
targetDate.setHours(0, 0, 0, 0);
|
|
2706
|
+
const diffMs = targetDate.getTime() - now.getTime();
|
|
2707
|
+
const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
|
|
2708
|
+
return {
|
|
2709
|
+
type: "date",
|
|
2710
|
+
input: query,
|
|
2711
|
+
result: diffDays,
|
|
2712
|
+
formatted: `${diffDays} days`,
|
|
2713
|
+
metadata: { from: now.toISOString(), to: target.toISOString() }
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2646
2717
|
const betweenMatch = lower.match(/^(?:days?\s+)?(?:between|from)\s+(.+)\s+(?:to|and|until)\s+(.+)$/);
|
|
2647
2718
|
if (betweenMatch) {
|
|
2648
2719
|
const d1 = parseFlexibleDate(betweenMatch[1].trim());
|
|
@@ -2735,6 +2806,82 @@ function formatDateResult(input, d, extraMeta) {
|
|
|
2735
2806
|
}
|
|
2736
2807
|
};
|
|
2737
2808
|
}
|
|
2809
|
+
function getNextOccurrence(month, day) {
|
|
2810
|
+
const now = /* @__PURE__ */ new Date();
|
|
2811
|
+
const thisYear = new Date(now.getFullYear(), month - 1, day);
|
|
2812
|
+
if (thisYear.getTime() >= new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()) {
|
|
2813
|
+
return thisYear;
|
|
2814
|
+
}
|
|
2815
|
+
return new Date(now.getFullYear() + 1, month - 1, day);
|
|
2816
|
+
}
|
|
2817
|
+
var NAMED_DATES = {
|
|
2818
|
+
"christmas": () => getNextOccurrence(12, 25),
|
|
2819
|
+
"christmas day": () => getNextOccurrence(12, 25),
|
|
2820
|
+
"christmas eve": () => getNextOccurrence(12, 24),
|
|
2821
|
+
"new year": () => getNextOccurrence(1, 1),
|
|
2822
|
+
"new years": () => getNextOccurrence(1, 1),
|
|
2823
|
+
"new year's": () => getNextOccurrence(1, 1),
|
|
2824
|
+
"new year's day": () => getNextOccurrence(1, 1),
|
|
2825
|
+
"new years day": () => getNextOccurrence(1, 1),
|
|
2826
|
+
"new years eve": () => getNextOccurrence(12, 31),
|
|
2827
|
+
"new year's eve": () => getNextOccurrence(12, 31),
|
|
2828
|
+
"valentines": () => getNextOccurrence(2, 14),
|
|
2829
|
+
"valentines day": () => getNextOccurrence(2, 14),
|
|
2830
|
+
"valentine's day": () => getNextOccurrence(2, 14),
|
|
2831
|
+
"halloween": () => getNextOccurrence(10, 31),
|
|
2832
|
+
"independence day": () => getNextOccurrence(7, 4),
|
|
2833
|
+
"st patricks day": () => getNextOccurrence(3, 17),
|
|
2834
|
+
"st patrick's day": () => getNextOccurrence(3, 17)
|
|
2835
|
+
};
|
|
2836
|
+
var MONTH_NAMES = {
|
|
2837
|
+
january: 1,
|
|
2838
|
+
jan: 1,
|
|
2839
|
+
february: 2,
|
|
2840
|
+
feb: 2,
|
|
2841
|
+
march: 3,
|
|
2842
|
+
mar: 3,
|
|
2843
|
+
april: 4,
|
|
2844
|
+
apr: 4,
|
|
2845
|
+
may: 5,
|
|
2846
|
+
june: 6,
|
|
2847
|
+
jun: 6,
|
|
2848
|
+
july: 7,
|
|
2849
|
+
jul: 7,
|
|
2850
|
+
august: 8,
|
|
2851
|
+
aug: 8,
|
|
2852
|
+
september: 9,
|
|
2853
|
+
sep: 9,
|
|
2854
|
+
sept: 9,
|
|
2855
|
+
october: 10,
|
|
2856
|
+
oct: 10,
|
|
2857
|
+
november: 11,
|
|
2858
|
+
nov: 11,
|
|
2859
|
+
december: 12,
|
|
2860
|
+
dec: 12
|
|
2861
|
+
};
|
|
2862
|
+
function parseMonthDay(str) {
|
|
2863
|
+
const cleaned = str.replace(/(\d+)(?:st|nd|rd|th)/gi, "$1");
|
|
2864
|
+
let match = cleaned.match(/^(\d{1,2})\s+([a-z]+)$/i);
|
|
2865
|
+
if (match) {
|
|
2866
|
+
const day = parseInt(match[1]);
|
|
2867
|
+
const month = MONTH_NAMES[match[2].toLowerCase()];
|
|
2868
|
+
if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
|
|
2869
|
+
}
|
|
2870
|
+
match = cleaned.match(/^([a-z]+)\s+(\d{1,2})$/i);
|
|
2871
|
+
if (match) {
|
|
2872
|
+
const month = MONTH_NAMES[match[1].toLowerCase()];
|
|
2873
|
+
const day = parseInt(match[2]);
|
|
2874
|
+
if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
|
|
2875
|
+
}
|
|
2876
|
+
return null;
|
|
2877
|
+
}
|
|
2878
|
+
function parseDateTarget(str) {
|
|
2879
|
+
const lower = str.toLowerCase().trim();
|
|
2880
|
+
if (NAMED_DATES[lower]) return NAMED_DATES[lower]();
|
|
2881
|
+
const monthDay = parseMonthDay(lower);
|
|
2882
|
+
if (monthDay) return monthDay;
|
|
2883
|
+
return parseFlexibleDate(lower);
|
|
2884
|
+
}
|
|
2738
2885
|
function parseFlexibleDate(str) {
|
|
2739
2886
|
if (str === "today" || str === "now" || str === "date") return /* @__PURE__ */ new Date();
|
|
2740
2887
|
if (str === "tomorrow") {
|
|
@@ -2873,6 +3020,46 @@ async function calculate(input, options) {
|
|
|
2873
3020
|
const locale = options?.locale ?? "en-US";
|
|
2874
3021
|
const precision = options?.precision ?? 10;
|
|
2875
3022
|
const rateProvider = options?.rateProvider ?? getDefaultProvider();
|
|
3023
|
+
const convMathMatch = trimmed.match(
|
|
3024
|
+
/^(.+?\s+(?:to|in)\s+[a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s*([+\-*/^])\s*(.+)$/i
|
|
3025
|
+
);
|
|
3026
|
+
if (convMathMatch) {
|
|
3027
|
+
const convPart = convMathMatch[1].trim();
|
|
3028
|
+
const operator = convMathMatch[2];
|
|
3029
|
+
const mathPart = convMathMatch[3].trim();
|
|
3030
|
+
const convIntent = detectIntent(convPart);
|
|
3031
|
+
if (convIntent.kind === "currency" || convIntent.kind === "crypto" || convIntent.kind === "unit") {
|
|
3032
|
+
let convResult;
|
|
3033
|
+
switch (convIntent.kind) {
|
|
3034
|
+
case "currency":
|
|
3035
|
+
convResult = await evaluateCurrency(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
|
|
3036
|
+
break;
|
|
3037
|
+
case "crypto":
|
|
3038
|
+
convResult = await evaluateCrypto(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
|
|
3039
|
+
break;
|
|
3040
|
+
case "unit":
|
|
3041
|
+
convResult = evaluateUnit(convIntent.amount, convIntent.from, convIntent.to, locale, precision);
|
|
3042
|
+
break;
|
|
3043
|
+
}
|
|
3044
|
+
if (convResult.type !== "error" && typeof convResult.result === "number") {
|
|
3045
|
+
const mathExpr = `${convResult.result} ${operator} ${mathPart}`;
|
|
3046
|
+
const mathResult = evaluateMath(mathExpr, locale, precision);
|
|
3047
|
+
if (mathResult.type !== "error") {
|
|
3048
|
+
return {
|
|
3049
|
+
...mathResult,
|
|
3050
|
+
type: convResult.type,
|
|
3051
|
+
input: trimmed,
|
|
3052
|
+
metadata: {
|
|
3053
|
+
...mathResult.metadata,
|
|
3054
|
+
conversionResult: convResult.result,
|
|
3055
|
+
conversionFormatted: convResult.formatted,
|
|
3056
|
+
operation: `${convResult.formatted} ${operator} ${mathPart}`
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
2876
3063
|
const intent = detectIntent(trimmed);
|
|
2877
3064
|
switch (intent.kind) {
|
|
2878
3065
|
case "time":
|
package/dist/index.mjs
CHANGED
|
@@ -1833,7 +1833,10 @@ var DATE_PATTERNS = [
|
|
|
1833
1833
|
/^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/i,
|
|
1834
1834
|
/^(?:(?:what(?:'s|s| is))\s+today|(?:what(?:'s|s| is))\s+today'?s date|what day is it|what date is .+|todays date|today'?s date|date today|current date|what day is .+|when is .+)$/i,
|
|
1835
1835
|
// Days between dates
|
|
1836
|
-
/^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i
|
|
1836
|
+
/^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i,
|
|
1837
|
+
// Days until
|
|
1838
|
+
/^(?:days?\s+)?(?:until|till|to)\s+.+$/i,
|
|
1839
|
+
/^(?:how many days?\s+)?(?:until|till|to|before)\s+.+$/i
|
|
1837
1840
|
];
|
|
1838
1841
|
function tryDateIntent(input) {
|
|
1839
1842
|
for (const pattern of DATE_PATTERNS) {
|
|
@@ -1843,12 +1846,31 @@ function tryDateIntent(input) {
|
|
|
1843
1846
|
}
|
|
1844
1847
|
return null;
|
|
1845
1848
|
}
|
|
1846
|
-
var CONVERSION_PATTERN = /^([\d.,]+)?\s*([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)$/i;
|
|
1849
|
+
var CONVERSION_PATTERN = /^([\d.,]+[kKmMbB]?)?\s*([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)$/i;
|
|
1850
|
+
function parseAmountWithSuffix(str) {
|
|
1851
|
+
if (!str) return 1;
|
|
1852
|
+
const m = str.match(/^([\d.,]+)([kKmMbB]?)$/);
|
|
1853
|
+
if (!m) return parseFloat(str) || 1;
|
|
1854
|
+
let numStr = m[1];
|
|
1855
|
+
const suffix = m[2].toLowerCase();
|
|
1856
|
+
if (numStr.includes(",")) {
|
|
1857
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
1858
|
+
const afterComma = numStr.slice(lastComma + 1);
|
|
1859
|
+
if (afterComma.length <= 2 && !numStr.includes(".")) {
|
|
1860
|
+
numStr = numStr.slice(0, lastComma) + "." + afterComma;
|
|
1861
|
+
} else {
|
|
1862
|
+
numStr = numStr.replace(/,/g, "");
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
const base = parseFloat(numStr);
|
|
1866
|
+
const multipliers = { k: 1e3, m: 1e6, b: 1e9 };
|
|
1867
|
+
return isNaN(base) ? 1 : base * (multipliers[suffix] ?? 1);
|
|
1868
|
+
}
|
|
1847
1869
|
function tryCurrencyOrCryptoIntent(input) {
|
|
1848
1870
|
const normalized = normalizeConversionInput(input);
|
|
1849
1871
|
const match = normalized.match(CONVERSION_PATTERN);
|
|
1850
1872
|
if (!match) return null;
|
|
1851
|
-
const amount =
|
|
1873
|
+
const amount = parseAmountWithSuffix(match[1]);
|
|
1852
1874
|
const fromToken = match[2].trim();
|
|
1853
1875
|
const toToken = match[3].trim();
|
|
1854
1876
|
const fromCrypto = resolveCrypto(fromToken);
|
|
@@ -1866,13 +1888,13 @@ function tryCurrencyOrCryptoIntent(input) {
|
|
|
1866
1888
|
}
|
|
1867
1889
|
return null;
|
|
1868
1890
|
}
|
|
1869
|
-
var UNIT_PATTERN = /^(-?[\d.,]+)\s*([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)$/i;
|
|
1870
|
-
var UNIT_PATTERN_NO_SPACE = /^(-?[\d.,]+)([a-zA-Z°]+)\s+(?:to|in|into|as)\s+([a-zA-Z°]+(?:\s+[a-zA-Z]+)?)$/i;
|
|
1891
|
+
var UNIT_PATTERN = /^(-?[\d.,]+[kKmMbB]?)\s*([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)\s+(?:to|in|into|as|=)\s+([a-zA-Z°/µμ'"²³]+(?:\s+[a-zA-Z]+(?:\s+[a-zA-Z]+)?)?)$/i;
|
|
1892
|
+
var UNIT_PATTERN_NO_SPACE = /^(-?[\d.,]+[kKmMbB]?)([a-zA-Z°]+)\s+(?:to|in|into|as)\s+([a-zA-Z°]+(?:\s+[a-zA-Z]+)?)$/i;
|
|
1871
1893
|
function tryUnitIntent(input) {
|
|
1872
1894
|
const normalized = normalizeConversionInput(input);
|
|
1873
1895
|
const match = normalized.match(UNIT_PATTERN) || normalized.match(UNIT_PATTERN_NO_SPACE);
|
|
1874
1896
|
if (!match) return null;
|
|
1875
|
-
const amount =
|
|
1897
|
+
const amount = parseAmountWithSuffix(match[1]);
|
|
1876
1898
|
const fromToken = match[2].trim().toLowerCase();
|
|
1877
1899
|
const toToken = match[3].trim().toLowerCase();
|
|
1878
1900
|
const fromUnit = lookupUnit(fromToken);
|
|
@@ -2159,6 +2181,16 @@ var Parser = class {
|
|
|
2159
2181
|
if (this.pos < this.expr.length && /[+-]/.test(this.expr[this.pos])) this.pos++;
|
|
2160
2182
|
while (this.pos < this.expr.length && /[0-9]/.test(this.expr[this.pos])) this.pos++;
|
|
2161
2183
|
}
|
|
2184
|
+
const numEnd = this.pos;
|
|
2185
|
+
if (/[kKmMbB]/.test(this.charAt(this.pos)) && !/[a-zA-Z0-9_]/.test(this.charAt(this.pos + 1))) {
|
|
2186
|
+
const suffix = this.expr[this.pos].toLowerCase();
|
|
2187
|
+
this.pos++;
|
|
2188
|
+
const multipliers = { k: 1e3, m: 1e6, b: 1e9 };
|
|
2189
|
+
const numStr2 = this.expr.slice(start, numEnd);
|
|
2190
|
+
const num2 = parseFloat(numStr2);
|
|
2191
|
+
if (isNaN(num2)) throw new Error(`Invalid number: '${numStr2}'`);
|
|
2192
|
+
return num2 * multipliers[suffix];
|
|
2193
|
+
}
|
|
2162
2194
|
const numStr = this.expr.slice(start, this.pos);
|
|
2163
2195
|
const num = parseFloat(numStr);
|
|
2164
2196
|
if (isNaN(num)) throw new Error(`Invalid number: '${numStr}'`);
|
|
@@ -2263,6 +2295,25 @@ function evaluateMath(expression, locale = "en-US", precision = 10) {
|
|
|
2263
2295
|
};
|
|
2264
2296
|
}
|
|
2265
2297
|
}
|
|
2298
|
+
function normalizeEuropeanDecimalSeparators(expr) {
|
|
2299
|
+
let result = "";
|
|
2300
|
+
let depth = 0;
|
|
2301
|
+
for (let i = 0; i < expr.length; i++) {
|
|
2302
|
+
const ch = expr[i];
|
|
2303
|
+
if (ch === "(") depth++;
|
|
2304
|
+
else if (ch === ")") depth--;
|
|
2305
|
+
else if (ch === "," && depth === 0) {
|
|
2306
|
+
const prevIsDigit = i > 0 && /[0-9]/.test(expr[i - 1]);
|
|
2307
|
+
const nextIsDigit = i < expr.length - 1 && /[0-9]/.test(expr[i + 1]);
|
|
2308
|
+
if (prevIsDigit && nextIsDigit) {
|
|
2309
|
+
result += ".";
|
|
2310
|
+
continue;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
result += ch;
|
|
2314
|
+
}
|
|
2315
|
+
return result;
|
|
2316
|
+
}
|
|
2266
2317
|
function normalizeMathExpression(expression) {
|
|
2267
2318
|
let normalized = expression.trim().replace(/\s+/g, " ");
|
|
2268
2319
|
normalized = normalized.replace(/^(?:(?:what(?:'s|s| is))\s+the\s+|(?:what(?:'s|s| is))\s+|calculate\s+)/i, "");
|
|
@@ -2296,6 +2347,7 @@ function normalizeMathExpression(expression) {
|
|
|
2296
2347
|
}
|
|
2297
2348
|
}
|
|
2298
2349
|
normalized = normalized.replace(/\bdivided by\b/gi, "/").replace(/\btimes\b/gi, "*").replace(/\bplus\b/gi, "+").replace(/\bminus\b/gi, "-");
|
|
2350
|
+
normalized = normalizeEuropeanDecimalSeparators(normalized);
|
|
2299
2351
|
return normalized.replace(/\s+/g, " ").trim();
|
|
2300
2352
|
}
|
|
2301
2353
|
|
|
@@ -2606,6 +2658,25 @@ function evaluateDate(query) {
|
|
|
2606
2658
|
return formatDateResult(query, d, { iso: d.toISOString(), unix: Math.floor(d.getTime() / 1e3) });
|
|
2607
2659
|
}
|
|
2608
2660
|
}
|
|
2661
|
+
const daysUntilMatch = lower.match(/^(?:(?:how many )?days?\s+)?(?:until|till|to|before)\s+(.+)$/);
|
|
2662
|
+
if (daysUntilMatch) {
|
|
2663
|
+
const target = parseDateTarget(daysUntilMatch[1].trim());
|
|
2664
|
+
if (target) {
|
|
2665
|
+
const now = /* @__PURE__ */ new Date();
|
|
2666
|
+
now.setHours(0, 0, 0, 0);
|
|
2667
|
+
const targetDate = new Date(target);
|
|
2668
|
+
targetDate.setHours(0, 0, 0, 0);
|
|
2669
|
+
const diffMs = targetDate.getTime() - now.getTime();
|
|
2670
|
+
const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
|
|
2671
|
+
return {
|
|
2672
|
+
type: "date",
|
|
2673
|
+
input: query,
|
|
2674
|
+
result: diffDays,
|
|
2675
|
+
formatted: `${diffDays} days`,
|
|
2676
|
+
metadata: { from: now.toISOString(), to: target.toISOString() }
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2609
2680
|
const betweenMatch = lower.match(/^(?:days?\s+)?(?:between|from)\s+(.+)\s+(?:to|and|until)\s+(.+)$/);
|
|
2610
2681
|
if (betweenMatch) {
|
|
2611
2682
|
const d1 = parseFlexibleDate(betweenMatch[1].trim());
|
|
@@ -2698,6 +2769,82 @@ function formatDateResult(input, d, extraMeta) {
|
|
|
2698
2769
|
}
|
|
2699
2770
|
};
|
|
2700
2771
|
}
|
|
2772
|
+
function getNextOccurrence(month, day) {
|
|
2773
|
+
const now = /* @__PURE__ */ new Date();
|
|
2774
|
+
const thisYear = new Date(now.getFullYear(), month - 1, day);
|
|
2775
|
+
if (thisYear.getTime() >= new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()) {
|
|
2776
|
+
return thisYear;
|
|
2777
|
+
}
|
|
2778
|
+
return new Date(now.getFullYear() + 1, month - 1, day);
|
|
2779
|
+
}
|
|
2780
|
+
var NAMED_DATES = {
|
|
2781
|
+
"christmas": () => getNextOccurrence(12, 25),
|
|
2782
|
+
"christmas day": () => getNextOccurrence(12, 25),
|
|
2783
|
+
"christmas eve": () => getNextOccurrence(12, 24),
|
|
2784
|
+
"new year": () => getNextOccurrence(1, 1),
|
|
2785
|
+
"new years": () => getNextOccurrence(1, 1),
|
|
2786
|
+
"new year's": () => getNextOccurrence(1, 1),
|
|
2787
|
+
"new year's day": () => getNextOccurrence(1, 1),
|
|
2788
|
+
"new years day": () => getNextOccurrence(1, 1),
|
|
2789
|
+
"new years eve": () => getNextOccurrence(12, 31),
|
|
2790
|
+
"new year's eve": () => getNextOccurrence(12, 31),
|
|
2791
|
+
"valentines": () => getNextOccurrence(2, 14),
|
|
2792
|
+
"valentines day": () => getNextOccurrence(2, 14),
|
|
2793
|
+
"valentine's day": () => getNextOccurrence(2, 14),
|
|
2794
|
+
"halloween": () => getNextOccurrence(10, 31),
|
|
2795
|
+
"independence day": () => getNextOccurrence(7, 4),
|
|
2796
|
+
"st patricks day": () => getNextOccurrence(3, 17),
|
|
2797
|
+
"st patrick's day": () => getNextOccurrence(3, 17)
|
|
2798
|
+
};
|
|
2799
|
+
var MONTH_NAMES = {
|
|
2800
|
+
january: 1,
|
|
2801
|
+
jan: 1,
|
|
2802
|
+
february: 2,
|
|
2803
|
+
feb: 2,
|
|
2804
|
+
march: 3,
|
|
2805
|
+
mar: 3,
|
|
2806
|
+
april: 4,
|
|
2807
|
+
apr: 4,
|
|
2808
|
+
may: 5,
|
|
2809
|
+
june: 6,
|
|
2810
|
+
jun: 6,
|
|
2811
|
+
july: 7,
|
|
2812
|
+
jul: 7,
|
|
2813
|
+
august: 8,
|
|
2814
|
+
aug: 8,
|
|
2815
|
+
september: 9,
|
|
2816
|
+
sep: 9,
|
|
2817
|
+
sept: 9,
|
|
2818
|
+
october: 10,
|
|
2819
|
+
oct: 10,
|
|
2820
|
+
november: 11,
|
|
2821
|
+
nov: 11,
|
|
2822
|
+
december: 12,
|
|
2823
|
+
dec: 12
|
|
2824
|
+
};
|
|
2825
|
+
function parseMonthDay(str) {
|
|
2826
|
+
const cleaned = str.replace(/(\d+)(?:st|nd|rd|th)/gi, "$1");
|
|
2827
|
+
let match = cleaned.match(/^(\d{1,2})\s+([a-z]+)$/i);
|
|
2828
|
+
if (match) {
|
|
2829
|
+
const day = parseInt(match[1]);
|
|
2830
|
+
const month = MONTH_NAMES[match[2].toLowerCase()];
|
|
2831
|
+
if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
|
|
2832
|
+
}
|
|
2833
|
+
match = cleaned.match(/^([a-z]+)\s+(\d{1,2})$/i);
|
|
2834
|
+
if (match) {
|
|
2835
|
+
const month = MONTH_NAMES[match[1].toLowerCase()];
|
|
2836
|
+
const day = parseInt(match[2]);
|
|
2837
|
+
if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
|
|
2838
|
+
}
|
|
2839
|
+
return null;
|
|
2840
|
+
}
|
|
2841
|
+
function parseDateTarget(str) {
|
|
2842
|
+
const lower = str.toLowerCase().trim();
|
|
2843
|
+
if (NAMED_DATES[lower]) return NAMED_DATES[lower]();
|
|
2844
|
+
const monthDay = parseMonthDay(lower);
|
|
2845
|
+
if (monthDay) return monthDay;
|
|
2846
|
+
return parseFlexibleDate(lower);
|
|
2847
|
+
}
|
|
2701
2848
|
function parseFlexibleDate(str) {
|
|
2702
2849
|
if (str === "today" || str === "now" || str === "date") return /* @__PURE__ */ new Date();
|
|
2703
2850
|
if (str === "tomorrow") {
|
|
@@ -2836,6 +2983,46 @@ async function calculate(input, options) {
|
|
|
2836
2983
|
const locale = options?.locale ?? "en-US";
|
|
2837
2984
|
const precision = options?.precision ?? 10;
|
|
2838
2985
|
const rateProvider = options?.rateProvider ?? getDefaultProvider();
|
|
2986
|
+
const convMathMatch = trimmed.match(
|
|
2987
|
+
/^(.+?\s+(?:to|in)\s+[a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s*([+\-*/^])\s*(.+)$/i
|
|
2988
|
+
);
|
|
2989
|
+
if (convMathMatch) {
|
|
2990
|
+
const convPart = convMathMatch[1].trim();
|
|
2991
|
+
const operator = convMathMatch[2];
|
|
2992
|
+
const mathPart = convMathMatch[3].trim();
|
|
2993
|
+
const convIntent = detectIntent(convPart);
|
|
2994
|
+
if (convIntent.kind === "currency" || convIntent.kind === "crypto" || convIntent.kind === "unit") {
|
|
2995
|
+
let convResult;
|
|
2996
|
+
switch (convIntent.kind) {
|
|
2997
|
+
case "currency":
|
|
2998
|
+
convResult = await evaluateCurrency(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
|
|
2999
|
+
break;
|
|
3000
|
+
case "crypto":
|
|
3001
|
+
convResult = await evaluateCrypto(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
|
|
3002
|
+
break;
|
|
3003
|
+
case "unit":
|
|
3004
|
+
convResult = evaluateUnit(convIntent.amount, convIntent.from, convIntent.to, locale, precision);
|
|
3005
|
+
break;
|
|
3006
|
+
}
|
|
3007
|
+
if (convResult.type !== "error" && typeof convResult.result === "number") {
|
|
3008
|
+
const mathExpr = `${convResult.result} ${operator} ${mathPart}`;
|
|
3009
|
+
const mathResult = evaluateMath(mathExpr, locale, precision);
|
|
3010
|
+
if (mathResult.type !== "error") {
|
|
3011
|
+
return {
|
|
3012
|
+
...mathResult,
|
|
3013
|
+
type: convResult.type,
|
|
3014
|
+
input: trimmed,
|
|
3015
|
+
metadata: {
|
|
3016
|
+
...mathResult.metadata,
|
|
3017
|
+
conversionResult: convResult.result,
|
|
3018
|
+
conversionFormatted: convResult.formatted,
|
|
3019
|
+
operation: `${convResult.formatted} ${operator} ${mathPart}`
|
|
3020
|
+
}
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
2839
3026
|
const intent = detectIntent(trimmed);
|
|
2840
3027
|
switch (intent.kind) {
|
|
2841
3028
|
case "time":
|
package/package.json
CHANGED