Skip to content

Commit 8489042

Browse files
feat(mf2): Add :math function (unicode-org/message-format-wg#932)
1 parent 78dda2a commit 8489042

File tree

4 files changed

+60
-1
lines changed

4 files changed

+60
-1
lines changed

mf2/messageformat/src/functions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { MessageExpressionPart } from '../formatted-parts.js';
33
export type { MessageFunctionContext } from '../resolve/function-context.js';
44
export { type MessageDateTime, date, datetime, time } from './datetime.js';
55
export { type MessageFallback, fallback } from './fallback.js';
6+
export { math } from './math.js';
67
export { type MessageNumber, integer, number } from './number.js';
78
export { type MessageString, string } from './string.js';
89
export { type MessageUnknownValue, unknown } from './unknown.js';
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { MessageResolutionError } from '../errors.js';
2+
import type { MessageFunctionContext } from './index.js';
3+
import { MessageNumber, number } from './number.js';
4+
import { asPositiveInteger } from './utils.js';
5+
6+
/**
7+
* `math` accepts a numeric value as input and adds or subtracts an integer value from it
8+
*
9+
* @beta
10+
*/
11+
export function math(
12+
ctx: MessageFunctionContext,
13+
mathOpt: Record<string | symbol, unknown>,
14+
input?: unknown
15+
): MessageNumber {
16+
const { source } = ctx;
17+
let options: unknown = undefined;
18+
let value = input;
19+
if (typeof value === 'object') {
20+
const valueOf = value?.valueOf;
21+
if (typeof valueOf === 'function') {
22+
options = (value as { options: unknown }).options;
23+
value = valueOf.call(value);
24+
}
25+
}
26+
if (typeof value === 'string') {
27+
try {
28+
value = JSON.parse(value);
29+
} catch {
30+
// handled below
31+
}
32+
}
33+
if (typeof value !== 'bigint' && typeof value !== 'number') {
34+
const msg = 'Input is not numeric';
35+
throw new MessageResolutionError('bad-operand', msg, source);
36+
}
37+
38+
let add: number;
39+
let sub: number;
40+
try {
41+
add = 'add' in mathOpt ? asPositiveInteger(mathOpt.add) : -1;
42+
sub = 'subtract' in mathOpt ? asPositiveInteger(mathOpt.subtract) : -1;
43+
} catch (error) {
44+
throw new MessageResolutionError('bad-option', String(error), source);
45+
}
46+
if (add < 0 === sub < 0) {
47+
const msg =
48+
'Exactly one of "add" or "subtract" is required as a :math option';
49+
throw new MessageResolutionError('bad-option', msg, source);
50+
}
51+
const delta = add < 0 ? -sub : add;
52+
if (typeof value === 'number') value += delta;
53+
else value += BigInt(delta);
54+
55+
return number(ctx, {}, { valueOf: () => value, options });
56+
}

mf2/messageformat/src/messageformat.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
date,
1111
datetime,
1212
integer,
13+
math,
1314
number,
1415
string,
1516
time
@@ -24,6 +25,7 @@ const defaultFunctions = Object.freeze({
2425
date,
2526
datetime,
2627
integer,
28+
math,
2729
number,
2830
string,
2931
time

0 commit comments

Comments
 (0)