Skip to content

Commit 9d46225

Browse files
authored
[WIP] Add array utility functions for expression evaluator (#34)
* Add array utility and type checking functions with tests * Add documentation for new array and type checking functions
1 parent 06f8773 commit 9d46225

8 files changed

Lines changed: 716 additions & 2 deletions

File tree

docs/syntax.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,13 @@ Besides the "operator" functions, there are several pre-defined functions. You c
134134
| count(a) | Returns the number of items in an array. |
135135
| map(f, a) | Array map: Pass each element of `a` the function `f`, and return an array of the results. |
136136
| fold(f, y, a) | Array fold: Fold/reduce array `a` into a single value, `y` by setting `y = f(y, x, index)` for each element `x` of the array. |
137+
| reduce(f, y, a) | Alias for `fold`. Reduces array `a` into a single value using function `f` starting with accumulator `y`. |
137138
| filter(f, a) | Array filter: Return an array containing only the values from `a` where `f(x, index)` is `true`. |
139+
| find(f, a) | Returns the first element in array `a` where `f(x, index)` is `true`, or `undefined` if not found. |
140+
| some(f, a) | Returns `true` if at least one element in array `a` satisfies `f(x, index)`, `false` otherwise. |
141+
| every(f, a) | Returns `true` if all elements in array `a` satisfy `f(x, index)`. Returns `true` for empty arrays. |
142+
| unique(a) | Returns a new array with duplicate values removed from array `a`. |
143+
| distinct(a) | Alias for `unique`. Returns a new array with duplicate values removed. |
138144
| indexOf(x, a) | Return the first index of string or array `a` matching the value `x`, or `-1` if not found. |
139145
| join(sep, a) | Concatenate the elements of `a`, separated by `sep`. |
140146
| naturalSort(arr) | Sorts an array of strings using natural sort order (alphanumeric-aware). For example, `["file10", "file2", "file1"]` becomes `["file1", "file2", "file10"]`. |
@@ -146,6 +152,19 @@ Besides the "operator" functions, there are several pre-defined functions. You c
146152
| if(c, a, b) | Function form of c ? a : b. Note: This always evaluates both `a` and `b`, regardless of whether `c` is `true` or not. Use `c ? a : b` instead if there are side effects, or if evaluating the branches could be expensive. |
147153
| coalesce(a, b, ...) | Returns the first non-null and non-empty string value from the arguments. Numbers and booleans (including 0 and false) are considered valid values. |
148154

155+
### Type Checking Functions
156+
157+
| Function | Description |
158+
|:------------- |:----------- |
159+
| isArray(v) | Returns `true` if `v` is an array, `false` otherwise. |
160+
| isObject(v) | Returns `true` if `v` is an object (excluding null and arrays), `false` otherwise. |
161+
| isNumber(v) | Returns `true` if `v` is a number, `false` otherwise. |
162+
| isString(v) | Returns `true` if `v` is a string, `false` otherwise. |
163+
| isBoolean(v) | Returns `true` if `v` is a boolean, `false` otherwise. |
164+
| isNull(v) | Returns `true` if `v` is null, `false` otherwise. |
165+
| isUndefined(v)| Returns `true` if `v` is undefined, `false` otherwise. |
166+
| isFunction(v) | Returns `true` if `v` is a function, `false` otherwise. |
167+
149168
## String Functions
150169

151170
The parser includes comprehensive string manipulation capabilities.
@@ -403,6 +422,106 @@ map(x => x > 5 ? "high" : "low", [3, 7, 2, 9]) // Using ternary operator
403422

404423
> **Note:** Arrow functions share the same `fndef` operator flag as traditional function definitions. If function definitions are disabled via parser options, arrow functions will also be disabled.
405424
425+
### Examples of New Array Functions
426+
427+
The new array utility functions provide additional ways to work with arrays:
428+
429+
**Using reduce (alias for fold):**
430+
431+
```js
432+
reduce((acc, x) => acc + x, 0, [1, 2, 3, 4]) // 10 (sum using reduce)
433+
reduce((acc, x) => acc * x, 1, [2, 3, 4]) // 24 (product)
434+
```
435+
436+
**Using find:**
437+
438+
```js
439+
find(x => x > 5, [1, 3, 7, 2, 9]) // 7 (first element > 5)
440+
find(x => x < 0, [1, 2, 3]) // undefined (not found)
441+
find(x => x.age > 18, users) // First user over 18
442+
```
443+
444+
**Using some and every:**
445+
446+
```js
447+
some(x => x > 10, [1, 5, 15, 3]) // true (at least one > 10)
448+
every(x => x > 0, [1, 2, 3, 4]) // true (all positive)
449+
every(x => x % 2 == 0, [2, 4, 5, 6]) // false (not all even)
450+
some(x => x < 0, [1, 2, 3]) // false (none negative)
451+
```
452+
453+
**Using unique/distinct:**
454+
455+
```js
456+
unique([1, 2, 2, 3, 3, 3, 4]) // [1, 2, 3, 4]
457+
distinct(["a", "b", "a", "c", "b"]) // ["a", "b", "c"]
458+
unique([]) // []
459+
```
460+
461+
**Combining array functions:**
462+
463+
```js
464+
// Filter positive numbers, remove duplicates, then double each
465+
unique(filter(x => x > 0, [1, -2, 3, 3, -4, 5, 1])) // [1, 3, 5]
466+
map(x => x * 2, unique([1, 2, 2, 3])) // [2, 4, 6]
467+
468+
// Find first even number greater than 5
469+
find(x => x % 2 == 0, filter(x => x > 5, [3, 7, 8, 9, 10])) // 8
470+
```
471+
472+
### Examples of Type Checking Functions
473+
474+
Type checking functions are useful for validating data types and conditional logic:
475+
476+
**Basic type checking:**
477+
478+
```js
479+
isArray([1, 2, 3]) // true
480+
isNumber(42) // true
481+
isString("hello") // true
482+
isBoolean(true) // true
483+
isNull(null) // true
484+
isUndefined(undefined) // true
485+
isObject({a: 1}) // true
486+
isFunction(abs) // true
487+
```
488+
489+
**Using with conditionals:**
490+
491+
```js
492+
if(isArray(x), count(x), 0) // Get array length or 0
493+
if(isNumber(x), x * 2, x) // Double if number
494+
if(isString(x), toUpper(x), x) // Uppercase if string
495+
```
496+
497+
**Using with filter:**
498+
499+
```js
500+
filter(isNumber, [1, "a", 2, "b", 3]) // [1, 2, 3]
501+
filter(isString, [1, "a", 2, "b", 3]) // ["a", "b"]
502+
```
503+
504+
**Using with some/every:**
505+
506+
```js
507+
some(isString, [1, 2, "hello", 3]) // true (has at least one string)
508+
every(isNumber, [1, 2, 3, 4]) // true (all are numbers)
509+
every(isNumber, [1, "a", 3]) // false (not all numbers)
510+
```
511+
512+
**Practical examples:**
513+
514+
```js
515+
// Count how many strings are in an array
516+
count(filter(isString, [1, "a", 2, "b", 3])) // 2
517+
518+
// Get the first number in a mixed array
519+
find(isNumber, ["a", "b", 3, "c", 5]) // 3
520+
521+
// Check if any value is null or undefined
522+
some(x => isNull(x) or isUndefined(x), data) // true/false
523+
```
524+
406525
## Custom JavaScript Functions
407526

408527
If you need additional functions that aren't supported out of the box, you can easily add them in your own code. Instances of the `Parser` class have a property called `functions` that's simply an object with all the functions that are in scope. You can add, replace, or delete any of the properties to customize what's available in the expressions. For example:

src/functions/array/operations.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,69 @@ export function count(array: any[] | undefined): number | undefined {
9595
}
9696
return array.length;
9797
}
98+
99+
export function reduce(f: Function, init: any, a: any[] | undefined): any {
100+
// reduce is an alias for fold
101+
return fold(f, init, a);
102+
}
103+
104+
export function find(f: Function, a: any[] | undefined): any {
105+
if (a === undefined) {
106+
return undefined;
107+
}
108+
if (typeof f !== 'function') {
109+
throw new Error('First argument to find is not a function');
110+
}
111+
if (!Array.isArray(a)) {
112+
throw new Error('Second argument to find is not an array');
113+
}
114+
return a.find(function (x: any, i: number): any {
115+
return f(x, i);
116+
});
117+
}
118+
119+
export function some(f: Function, a: any[] | undefined): boolean | undefined {
120+
if (a === undefined) {
121+
return undefined;
122+
}
123+
if (typeof f !== 'function') {
124+
throw new Error('First argument to some is not a function');
125+
}
126+
if (!Array.isArray(a)) {
127+
throw new Error('Second argument to some is not an array');
128+
}
129+
return a.some(function (x: any, i: number): any {
130+
return f(x, i);
131+
});
132+
}
133+
134+
export function every(f: Function, a: any[] | undefined): boolean | undefined {
135+
if (a === undefined) {
136+
return undefined;
137+
}
138+
if (typeof f !== 'function') {
139+
throw new Error('First argument to every is not a function');
140+
}
141+
if (!Array.isArray(a)) {
142+
throw new Error('Second argument to every is not an array');
143+
}
144+
return a.every(function (x: any, i: number): any {
145+
return f(x, i);
146+
});
147+
}
148+
149+
export function unique(a: any[] | undefined): any[] | undefined {
150+
if (a === undefined) {
151+
return undefined;
152+
}
153+
if (!Array.isArray(a)) {
154+
throw new Error('Argument to unique is not an array');
155+
}
156+
// Use Set to remove duplicates, then convert back to array
157+
return Array.from(new Set(a));
158+
}
159+
160+
export function distinct(a: any[] | undefined): any[] | undefined {
161+
// distinct is an alias for unique
162+
return unique(a);
163+
}

src/functions/utility/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99

1010
export * from './conditional.js';
1111
export * from './string-object.js';
12+
export * from './type-checking.js';
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Type checking utility functions
3+
* Provides functions to check the type of values
4+
*/
5+
6+
/**
7+
* Checks if a value is an array
8+
* @param value - The value to check
9+
* @returns True if the value is an array, false otherwise
10+
*/
11+
export function isArray(value: any): boolean {
12+
return Array.isArray(value);
13+
}
14+
15+
/**
16+
* Checks if a value is an object (and not null or array)
17+
* @param value - The value to check
18+
* @returns True if the value is an object (excluding null and arrays), false otherwise
19+
*/
20+
export function isObject(value: any): boolean {
21+
return value !== null && typeof value === 'object' && !Array.isArray(value);
22+
}
23+
24+
/**
25+
* Checks if a value is a number
26+
* @param value - The value to check
27+
* @returns True if the value is a number, false otherwise
28+
*/
29+
export function isNumber(value: any): boolean {
30+
return typeof value === 'number';
31+
}
32+
33+
/**
34+
* Checks if a value is a string
35+
* @param value - The value to check
36+
* @returns True if the value is a string, false otherwise
37+
*/
38+
export function isString(value: any): boolean {
39+
return typeof value === 'string';
40+
}
41+
42+
/**
43+
* Checks if a value is a boolean
44+
* @param value - The value to check
45+
* @returns True if the value is a boolean, false otherwise
46+
*/
47+
export function isBoolean(value: any): boolean {
48+
return typeof value === 'boolean';
49+
}
50+
51+
/**
52+
* Checks if a value is null
53+
* @param value - The value to check
54+
* @returns True if the value is null, false otherwise
55+
*/
56+
export function isNull(value: any): boolean {
57+
return value === null;
58+
}
59+
60+
/**
61+
* Checks if a value is undefined
62+
* @param value - The value to check
63+
* @returns True if the value is undefined, false otherwise
64+
*/
65+
export function isUndefined(value: any): boolean {
66+
return value === undefined;
67+
}
68+
69+
/**
70+
* Checks if a value is a function
71+
* @param value - The value to check
72+
* @returns True if the value is a function, false otherwise
73+
*/
74+
export function isFunctionValue(value: any): boolean {
75+
return typeof value === 'function';
76+
}
77+

0 commit comments

Comments
 (0)