Skip to content

Commit 3c270c7

Browse files
authored
Release 2.0.0-beta.1
2 parents fe71a58 + d420479 commit 3c270c7

File tree

9 files changed

+8396
-82
lines changed

9 files changed

+8396
-82
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,8 @@ Specification / Reference: [HTML](https://opensource.adobe.com/json-formula/doc/
5151
[JavaScript API](./doc/output/JSDOCS.md)
5252

5353
[Developer Instructions](./DEVELOPMENT.md)
54+
55+
# Beta 2.0.0 Documentation
56+
[HTML](https://opensource.adobe.com/json-formula/doc/output/json-formula-specification-2.0.0-beta.1.html)
57+
[PDF](https://opensource.adobe.com/json-formula/doc/output/json-formula-specification-2.0.0-beta.1.pdf)
58+

doc/output/json-formula-specification-2.0.0-beta.1.html

Lines changed: 8278 additions & 0 deletions
Large diffs are not rendered by default.
1.32 MB
Binary file not shown.

doc/spec.adoc

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ If the supplied data is not correct for the execution context, json-formula will
7676
* The left-hand operand of ordering comparison operators (`>`, `>=`, `<`, `\<=`) must be a string or number. Any other type shall be coerced to a number.
7777
* If the operands of an ordering comparison are different, they shall both be coerced to a number
7878
* Parameters to functions shall be coerced when there is a single viable coercion available. For example, if a null value is provided to a function that accepts a number or string, then coercion shall not happen, since a null value can be coerced to both types. Conversely if a string is provided to a function that accepts a number or array of numbers, then the string shall be coerced to a number, since there is no supported coercion to convert it to an array of numbers.
79-
* When functions accept a typed array, the function rules determine whether coercion may occur. Some functions (e.g. `avg()`) ignore array members of the wrong type. Other functions (e.g. `abs()`) coerce array members. If coercion may occur, then any provided array will have each of its members coerced to the expected type. e.g., if the input array is `[2,3,"6"]` and an array of numbers is expected, the array will be coerced to `[2,3,6]`.
8079

8180
The equality and inequality operators (`=`, `==`, `!=`, `<>`) do **not** perform type coercion. If operands are different types, the values are considered not equal.
8281

@@ -94,7 +93,7 @@ In all cases except ordering comparison, if the coercion is not possible, a `Typ
9493
eval([1,2,3] ~ 4, {}) -> [1,2,3,4]
9594
eval(123 < "124", {}) -> true
9695
eval("23" > 111, {}) -> false
97-
eval(avg(["2", "3", "4"]), {}) -> 3
96+
eval(avgA(["2", "3", "4"]), {}) -> 3
9897
eval(1 == "1", {}) -> false
9998
----
10099

@@ -130,7 +129,7 @@ In all cases except ordering comparison, if the coercion is not possible, a `Typ
130129
| null | boolean | false
131130
|===
132131

133-
An array may be coerced to another type of array as long as there is a supported coercion for the array content. For examples, just as a string can be coerced to a number, an array of strings may be coerced to an array of numbers.
132+
An array may be coerced to another type of array as long as there is a supported coercion for the array content. For example, just as a string can be coerced to a number, an array of strings may be coerced to an array of numbers.
134133

135134
Note that while strings, numbers and booleans may be coerced to arrays, they may not be coerced to a different type within that array. For example, a number cannot be coerced to an array of strings -- even though a number can be coerced to a string, and a string can be coerced to an array of strings.
136135

@@ -142,7 +141,7 @@ Note that while strings, numbers and booleans may be coerced to arrays, they may
142141
eval("\"$123.00\" + 1", {}) -> TypeError
143142
eval("truth is " & `true`, {}) -> "truth is true"
144143
eval(2 + `true`, {}) -> 3
145-
eval(avg(["20", "30"]), {}) -> 25
144+
eval(minA(["20", "30"]), {}) -> 20
146145
----
147146

148147
=== Date and Time Values
@@ -1204,8 +1203,7 @@ output.
12041203

12051204
=== Function parameters
12061205

1207-
Functions support the set of standard json-formula <<Data Types, data types>>. If the resolved arguments cannot be coerced to
1208-
match the types specified in the signature, a `TypeError` error occurs.
1206+
Functions support the set of standard json-formula <<Data Types, data types>>. If the parameters cannot be coerced to match the types specified in the signature, a `TypeError` error occurs.
12091207

12101208
As a shorthand, the type `any` is used to indicate that the function argument can be
12111209
any of (`array|object|number|string|boolean|null`).
@@ -1229,14 +1227,12 @@ does not exist, a `FunctionError` error is raised.
12291227
Many functions that process scalar values also allow for the processing of arrays of values. For example, the `round()` function may be called to process a single value: `round(1.2345, 2)` or to process an array of values: `round([1.2345, 2.3456], 2)`. The first call will return a single value, the second call will return an array of values.
12301228
When processing arrays of values, and where there is more than one parameter, each parameter is converted to an array so that the function processes each value in the set of arrays. From our example above, the call to `round([1.2345, 2.3456], 2)` would be processed as if it were `round([1.2345, 2.3456], [2, 2])`, and the result would be the same as: `[round(1.2345, 2), round(2.3456, 2)]`.
12311229

1232-
Functions that accept array parameters will also accept nested arrays. With nested arrays, aggregating functions (min(), max(), avg(), sum() etc.) will flatten the arrays. e.g.
1233-
1234-
`avg([2.1, 3.1, [4.1, 5.1]])` will be processed as `avg([2.1, 3.1, 4.1, 5.1])` and return `3.6`.
1230+
Functions that accept array parameters will also accept nested arrays. Aggregating functions (`min()`, `max()`, `avg()`, `sum()`, etc.) will flatten nested arrays. e.g. `avg([2.1, 3.1, [4.1, 5.1]])` will be processed as `avg([2.1, 3.1, 4.1, 5.1])` and return `3.6`.
12351231

12361232
Non-aggregating functions will return the same array hierarchy. e.g.
12371233

1238-
`upper("a", ["b"]]) => ["A", ["B"]]`
1239-
`round([2.12, 3.12, [4.12, 5.12]], 1)` will be processed as `round([2.12, 3.12, [4.12, 5.12]], [1, 1, [1, 1]])` and return `[2.1, 3.1, [4.1, 5.1]] `
1234+
`upper(["a", ["b"]]) => ["A", ["B"]]`
1235+
`round([2.12, 3.12, [4.12, 5.12]], 1)` will be processed as `round([2.12, 3.12, [4.12, 5.12]], [1, 1, [1, 1]])` and return `[2.1, 3.1, [4.1, 5.1]]`
12401236

12411237
These array balancing rules apply when any parameter is an array:
12421238

@@ -1246,6 +1242,7 @@ These array balancing rules apply when any parameter is an array:
12461242
* The function will return an array which is the result of iterating over the elements of the arrays and applying the function logic on the values at the same index.
12471243

12481244
With nested arrays:
1245+
12491246
* Nested arrays will be flattened for aggregating functions
12501247
* Non-aggregating functions will preserve the array hierarchy and will apply the balancing rules to each element of the nested arrays
12511248

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adobe/json-formula",
3-
"version": "1.1.2",
3+
"version": "2.0.0-beta.1",
44
"description": "json-formula Grammar and implementation",
55
"main": "src/json-formula.js",
66
"type": "module",

src/functions.js

Lines changed: 84 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,10 @@ export default function functions(
440440

441441
/**
442442
* Finds the average of the elements in an array.
443-
* Non-numeric values (text, boolean, null etc) are ignored.
443+
* Non-numeric values (text, boolean, null, object) are ignored.
444444
* If there are nested arrays, they are flattened.
445445
* If the array is empty, an evaluation error is thrown
446-
* @param {number[]} elements array of numeric values
446+
* @param {any[]} elements array of values
447447
* @return {number} average value
448448
* @function avg
449449
* @example
@@ -467,7 +467,7 @@ export default function functions(
467467

468468
/**
469469
* Finds the average of the elements in an array, converting strings and booleans to number.
470-
* If any conversions to number fail, an type error is thrown.
470+
* If any conversions to number fail, a type error is thrown.
471471
* If there are nested arrays, they are flattened.
472472
* If the array is empty, an evaluation error is thrown
473473
* @param {number[]} elements array of numeric values
@@ -1200,7 +1200,7 @@ export default function functions(
12001200
* @returns {string|string[]} the lower case value of the input string
12011201
* @function lower
12021202
* @example
1203-
* lower("E. E. Cummings") // returns e. e. cummings
1203+
* lower("E. E. Cummings") // returns "e. e. cummings"
12041204
*/
12051205
lower: {
12061206
_func: args => evaluate(args, a => toString(a).toLowerCase()),
@@ -1230,10 +1230,10 @@ export default function functions(
12301230

12311231
/**
12321232
* Calculates the largest value in the input numbers.
1233-
* Any values that are not numbers (e.g. null, boolean, strings, objects) will be ignored.
1233+
* Any values that are not numbers (null, boolean, strings, objects) will be ignored.
12341234
* If any parameters are arrays, the arrays will be flattened.
12351235
* If no numbers are provided, the function will return zero.
1236-
* @param {...(number[]|number)} collection values/array(s) in which the maximum
1236+
* @param {...(array|any)} collection values/array(s) in which the maximum
12371237
* element is to be calculated
12381238
* @return {number} the largest value found
12391239
* @function max
@@ -1263,7 +1263,7 @@ export default function functions(
12631263
* Calculates the largest value in the input values, coercing parameters to numbers.
12641264
* Null values are ignored.
12651265
* If any parameters cannot be converted to a number,
1266-
* the function will fail with an type error.
1266+
* the function will fail with a type error.
12671267
* If any parameters are arrays, the arrays will be flattened.
12681268
* If no numbers are provided, the function will return zero.
12691269
* @param {...(any)} collection values/array(s) in which the maximum
@@ -1379,10 +1379,10 @@ export default function functions(
13791379

13801380
/**
13811381
* Calculates the smallest value in the input numbers.
1382-
* Any values that are not numbers (e.g. null, boolean, strings, objects) will be ignored.
1382+
* Any values that are not numbers (null, boolean, string, object) will be ignored.
13831383
* If any parameters are arrays, the arrays will be flattened.
13841384
* If no numbers are provided, the function will return zero.
1385-
* @param {...(number[]|number)} collection
1385+
* @param {...(any[]|any)} collection
13861386
* Values/arrays to search for the minimum value
13871387
* @return {number} the smallest value found
13881388
* @function min
@@ -1410,10 +1410,10 @@ export default function functions(
14101410
* Calculates the smallest value in the input values, coercing parameters to numbers.
14111411
* Null values are ignored.
14121412
* If any parameters cannot be converted to a number,
1413-
* the function will fail with an type error.
1413+
* the function will fail with a type error.
14141414
* If any parameters are arrays, the arrays will be flattened.
14151415
* If no numbers are provided, the function will return zero.
1416-
* @param {...(any)} collection values/array(s) in which the maximum
1416+
* @param {...(any[]|any)} collection values/array(s) in which the maximum
14171417
* element is to be calculated
14181418
* @return {number} the largest value found
14191419
* @function minA
@@ -2148,13 +2148,13 @@ export default function functions(
21482148
},
21492149

21502150
/**
2151-
* Find the square root of a number
2152-
* @param {number|number[]} num source number
2153-
* @return {number|number[]} The calculated square root value
2154-
* @function sqrt
2155-
* @example
2156-
* sqrt(4) // returns 2
2157-
*/
2151+
* Find the square root of a number
2152+
* @param {number|number[]} num source number
2153+
* @return {number|number[]} The calculated square root value
2154+
* @function sqrt
2155+
* @example
2156+
* sqrt(4) // returns 2
2157+
*/
21582158
sqrt: {
21592159
_func: args => evaluate(args, arg => validNumber(Math.sqrt(arg), 'sqrt')),
21602160
_signature: [
@@ -2185,7 +2185,7 @@ export default function functions(
21852185
* then compute the standard deviation using [stdevp]{@link stdevp}.
21862186
* Non-numeric values (text, boolean, null etc) are ignored.
21872187
* If there are nested arrays, they are flattened.
2188-
* @param {number[]} numbers The array of numbers comprising the population.
2188+
* @param {any[]} values The array containing numbers comprising the population.
21892189
* Array size must be greater than 1.
21902190
* @returns {number} [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation)
21912191
* @function stdev
@@ -2252,7 +2252,9 @@ export default function functions(
22522252
* `stdevp` assumes that its arguments are the entire population.
22532253
* If your data represents a sample of the population,
22542254
* then compute the standard deviation using [stdev]{@link stdev}.
2255-
* @param {number[]} numbers The array of numbers comprising the population.
2255+
* Non-numeric values (text, boolean, null etc) are ignored.
2256+
* If there are nested arrays, they are flattened.
2257+
* @param {any[]} values The array containing numbers comprising the population.
22562258
* An empty array is not allowed.
22572259
* @returns {number} Calculated standard deviation
22582260
* @function stdevp
@@ -2359,7 +2361,9 @@ export default function functions(
23592361
/**
23602362
* Calculates the sum of the provided array.
23612363
* An empty array will produce a return value of 0.
2362-
* @param {number[]} collection array of numbers
2364+
* Any values that are not numbers (null, boolean, strings, objects) will be ignored.
2365+
* If any parameters are arrays, the arrays will be flattened.
2366+
* @param {any[]} collection array of values
23632367
* @return {number} The computed sum
23642368
* @function sum
23652369
* @example
@@ -2368,13 +2372,18 @@ export default function functions(
23682372
sum: {
23692373
_func: resolvedArgs => {
23702374
let sum = 0;
2371-
resolvedArgs[0].flat(Infinity).forEach(arg => {
2372-
sum += arg * 1;
2373-
});
2375+
resolvedArgs[0]
2376+
.flat(Infinity)
2377+
.filter(a => getType(a) === TYPE_NUMBER)
2378+
.forEach(arg => {
2379+
sum += arg * 1;
2380+
});
2381+
23742382
return sum;
23752383
},
2376-
_signature: [{ types: [TYPE_ARRAY_NUMBER] }],
2384+
_signature: [{ types: [TYPE_ARRAY] }],
23772385
},
2386+
23782387
/**
23792388
* Computes the tangent of a number in radians
23802389
* @param {number|number[]} angle A number representing an angle in radians.
@@ -2517,11 +2526,14 @@ export default function functions(
25172526
},
25182527

25192528
/**
2520-
* Converts the provided arg to a number as per
2521-
* the <<_type_coercion_rules,type coercion rules>>.
2529+
* Converts the provided arg to a number.
2530+
* The conversions follow the <<_type_coercion_rules,type coercion rules>> but will also:
2531+
* * Convert non-numeric strings to zero
2532+
* * Convert arrays to arrays of numbers
25222533
*
2523-
* @param {any} arg to convert to number
2524-
* @param {integer} [base=10] If the input `arg` is a string, the use base to convert to number.
2534+
* @param {any|any[]} value to convert to number
2535+
* @param {integer|integer[]} [base=10] If the input `arg` is a string,
2536+
* the base to use to convert to number.
25252537
* One of: 2, 8, 10, 16. Defaults to 10.
25262538
* @return {number} The resulting number. If conversion to number fails, return null.
25272539
* @function toNumber
@@ -2535,49 +2547,57 @@ export default function functions(
25352547
*/
25362548
toNumber: {
25372549
_func: resolvedArgs => {
2538-
const num = valueOf(resolvedArgs[0]);
2539-
const base = resolvedArgs.length > 1 ? toInteger(resolvedArgs[1]) : 10;
2540-
if (getType(num) === TYPE_STRING && base !== 10) {
2541-
let digitCheck;
2542-
if (base === 2) digitCheck = /^\s*(\+|-)?[01.]+\s*$/;
2543-
else if (base === 8) digitCheck = /^\s*(\+|-)?[0-7.]+\s*$/;
2544-
else if (base === 16) digitCheck = /^\s*(\+|-)?[0-9A-Fa-f.]+\s*$/;
2545-
else throw evaluationError(`Invalid base: "${base}" for toNumber()`);
2546-
2547-
if (num === '') return 0;
2548-
if (!digitCheck.test(num)) {
2549-
debug.push(`Failed to convert "${num}" base "${base}" to number`);
2550-
return null;
2551-
}
2552-
const parts = num.split('.').map(p => p.trim());
2550+
const toNumberFn = (value, base) => {
2551+
const num = valueOf(value);
2552+
if (getType(num) === TYPE_STRING && base !== 10) {
2553+
let digitCheck;
2554+
if (base === 2) digitCheck = /^\s*(\+|-)?[01.]+\s*$/;
2555+
else if (base === 8) digitCheck = /^\s*(\+|-)?[0-7.]+\s*$/;
2556+
else if (base === 16) digitCheck = /^\s*(\+|-)?[0-9A-Fa-f.]+\s*$/;
2557+
else throw evaluationError(`Invalid base: "${base}" for toNumber()`);
2558+
2559+
if (num === '') return 0;
2560+
if (!digitCheck.test(num)) {
2561+
debug.push(`Failed to convert "${num}" base "${base}" to number`);
2562+
return null;
2563+
}
2564+
const parts = num.split('.').map(p => p.trim());
25532565

2554-
let decimal = 0;
2555-
if (parts.length > 1) {
2556-
decimal = parseInt(parts[1], base) * base ** -parts[1].length;
2557-
}
2566+
let decimal = 0;
2567+
if (parts.length > 1) {
2568+
decimal = parseInt(parts[1], base) * base ** -parts[1].length;
2569+
}
25582570

2559-
const result = parseInt(parts[0], base) + decimal;
2560-
if (parts.length > 2 || Number.isNaN(result)) {
2561-
debug.push(`Failed to convert "${num}" base "${base}" to number`);
2571+
const result = parseInt(parts[0], base) + decimal;
2572+
if (parts.length > 2 || Number.isNaN(result)) {
2573+
debug.push(`Failed to convert "${num}" base "${base}" to number`);
2574+
return null;
2575+
}
2576+
return result;
2577+
}
2578+
try {
2579+
return toNumber(num);
2580+
} catch (e) {
2581+
const errorString = arg => {
2582+
const v = toJSON(arg);
2583+
return v.length > 50 ? `${v.substring(0, 20)} ...` : v;
2584+
};
2585+
2586+
debug.push(`Failed to convert "${errorString(num)}" to number`);
25622587
return null;
25632588
}
2564-
return result;
2565-
}
2566-
try {
2567-
return toNumber(num);
2568-
} catch (e) {
2569-
const errorString = arg => {
2570-
const v = toJSON(arg);
2571-
return v.length > 50 ? `${v.substring(0, 20)} ...` : v;
2572-
};
2573-
2574-
debug.push(`Failed to convert "${errorString(num)}" to number`);
2575-
return null;
2589+
};
2590+
let base = 10;
2591+
if (resolvedArgs.length > 1) {
2592+
base = Array.isArray(resolvedArgs[1])
2593+
? resolvedArgs.map(toInteger)
2594+
: toInteger(resolvedArgs[1]);
25762595
}
2596+
return evaluate([resolvedArgs[0], base], toNumberFn);
25772597
},
25782598
_signature: [
25792599
{ types: [TYPE_ANY] },
2580-
{ types: [TYPE_NUMBER], optional: true },
2600+
{ types: [TYPE_NUMBER, TYPE_ARRAY_NUMBER], optional: true },
25812601
],
25822602
},
25832603

test/functions.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,19 @@
897897
},
898898
{
899899
"expression": "sum(array)",
900-
"error": "TypeError"
900+
"result": 11
901+
},
902+
{
903+
"expression": "sum(toNumber(array))",
904+
"result": 111
905+
},
906+
{
907+
"expression": "toNumber(array, 16)",
908+
"result": [-1, 3, 4, 5, 10, 256]
909+
},
910+
{
911+
"expression": "sum(toNumber(array, 16))",
912+
"result": 277
901913
},
902914
{
903915
"expression": "sum(array[].toNumber(@))",
@@ -1037,7 +1049,7 @@
10371049
},
10381050
{
10391051
"expression": "toNumber(`[0]`)",
1040-
"result": null
1052+
"result": [0]
10411053
},
10421054
{
10431055
"expression": "toNumber(`{\"foo\": 0}`)",

0 commit comments

Comments
 (0)