Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,13 @@ Besides the "operator" functions, there are several pre-defined functions. You c
| fac(n) | n! (factorial of n: "n * (n-1) * (n-2) * … * 2 * 1") Deprecated. Use the ! operator instead. |
| min(a,b,…) | Get the smallest (minimum) number in the list. |
| max(a,b,…) | Get the largest (maximum) number in the list. |
| clamp(x, min, max) | Clamps x to be within the range [min, max]. Returns min if x < min, max if x > max, otherwise x. |
| hypot(a,b) | Hypotenuse, i.e. the square root of the sum of squares of its arguments. |
| pyt(a, b) | Alias for hypot. |
| pow(x, y) | Equivalent to x^y. For consistency with JavaScript's Math object. |
| atan2(y, x) | Arc tangent of x/y. i.e. the angle between (0, 0) and (x, y) in radians. |
| roundTo(x, n) | Rounds x to n places after the decimal point. |
| count(a) | Returns the number of items in an array. |
| map(f, a) | Array map: Pass each element of `a` the function `f`, and return an array of the results. |
| 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. |
| filter(f, a) | Array filter: Return an array containing only the values from `a` where `f(x, index)` is `true`. |
Expand Down
10 changes: 10 additions & 0 deletions src/functions/array/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,13 @@ export function sum(array: (number | undefined)[] | undefined): number | undefin
return total + (value === undefined ? 0 : Number(value));
}, 0);
}

export function count(array: any[] | undefined): number | undefined {
if (array === undefined) {
return undefined;
}
if (!Array.isArray(array)) {
throw new Error('Count argument is not an array');
}
return array.length;
}
7 changes: 7 additions & 0 deletions src/functions/math/advanced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,10 @@ export function roundTo(value: number | undefined, exp?: number): number | undef
const resultStr = shiftedValue.toString().split('e');
return +(resultStr[0] + 'e' + (resultStr[1] ? (+resultStr[1] + numExp) : numExp));
}

export function clamp(value: number | undefined, minVal: number | undefined, maxVal: number | undefined): number | undefined {
if (value === undefined || minVal === undefined || maxVal === undefined) {
return undefined;
}
return Math.min(Math.max(value, minVal), maxVal);
}
16 changes: 16 additions & 0 deletions src/language-service/language-service.documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,22 @@ export const BUILTIN_FUNCTION_DOCS: Record<string, FunctionDoc> = {
{ name: 'a', description: 'Array of numbers.' }
]
},
count: {
name: 'count',
description: 'Returns the number of items in an array.',
params: [
{ name: 'a', description: 'Array to count.' }
]
},
clamp: {
name: 'clamp',
description: 'Clamps a value between a minimum and maximum.',
params: [
{ name: 'value', description: 'The value to clamp.' },
{ name: 'min', description: 'Minimum allowed value.' },
{ name: 'max', description: 'Maximum allowed value.' }
]
},
/**
* String functions
*/
Expand Down
4 changes: 3 additions & 1 deletion src/parsing/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Expression } from '../core/expression.js';
import type { Value, VariableResolveResult, Values } from '../types/values.js';
import type { Instruction } from './instruction.js';
import type { OperatorFunction } from '../types/parser.js';
import { atan2, condition, fac, filter, fold, gamma, hypot, indexOf, join, map, max, min, random, roundTo, sum, json, stringLength, isEmpty, stringContains, startsWith, endsWith, searchCount, trim, toUpper, toLower, toTitle, split, repeat, reverse, left, right, replace, replaceFirst, naturalSort, toNumber, toBoolean, padLeft, padRight, padBoth, slice, urlEncode, base64Encode, base64Decode, coalesceString, merge, keys, values, flatten } from '../functions/index.js';
import { atan2, condition, fac, filter, fold, gamma, hypot, indexOf, join, map, max, min, random, roundTo, sum, json, stringLength, isEmpty, stringContains, startsWith, endsWith, searchCount, trim, toUpper, toLower, toTitle, split, repeat, reverse, left, right, replace, replaceFirst, naturalSort, toNumber, toBoolean, padLeft, padRight, padBoth, slice, urlEncode, base64Encode, base64Decode, coalesceString, merge, keys, values, flatten, count, clamp } from '../functions/index.js';
import {
add,
sub,
Expand Down Expand Up @@ -180,6 +180,8 @@ export class Parser {

this.functions = {
atan2: atan2,
clamp: clamp,
count: count,
fac: fac,
filter: filter,
fold: fold,
Expand Down
37 changes: 37 additions & 0 deletions test/functions/functions-advanced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,41 @@ describe('Advanced Functions TypeScript Test', function () {
assert.strictEqual(parser.evaluate('if(2 > 5, 1, undefined)'), undefined);
});
});

describe('clamp(value, min, max)', function () {
it('should return the value when within bounds', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('clamp(5, 0, 10)'), 5);
assert.strictEqual(parser.evaluate('clamp(0, 0, 10)'), 0);
assert.strictEqual(parser.evaluate('clamp(10, 0, 10)'), 10);
});
it('should return min when value is below min', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('clamp(-5, 0, 10)'), 0);
assert.strictEqual(parser.evaluate('clamp(-100, -10, 10)'), -10);
});
it('should return max when value is above max', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('clamp(15, 0, 10)'), 10);
assert.strictEqual(parser.evaluate('clamp(100, -10, 10)'), 10);
});
it('should work with negative ranges', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('clamp(-5, -10, -1)'), -5);
assert.strictEqual(parser.evaluate('clamp(0, -10, -1)'), -1);
assert.strictEqual(parser.evaluate('clamp(-15, -10, -1)'), -10);
});
it('should work with decimal values', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('clamp(5.5, 0, 10)'), 5.5);
assert.strictEqual(parser.evaluate('clamp(0.1, 0.2, 0.9)'), 0.2);
assert.strictEqual(parser.evaluate('clamp(0.95, 0.2, 0.9)'), 0.9);
});
it('should return undefined if any argument is undefined', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('clamp(undefined, 0, 10)'), undefined);
assert.strictEqual(parser.evaluate('clamp(5, undefined, 10)'), undefined);
assert.strictEqual(parser.evaluate('clamp(5, 0, undefined)'), undefined);
});
});
});
24 changes: 24 additions & 0 deletions test/functions/functions-array-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,30 @@ describe('Array and String Functions TypeScript Test', function () {
});
});

describe('count(array)', function () {
it('should return zero with an empty array', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('count([])'), 0);
});
it('should work on a single-element array', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('count([5])'), 1);
});
it('should work on a multi-element array', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('count([1, 2, 3, 4])'), 4);
assert.strictEqual(parser.evaluate('count(["a", "b", "c"])'), 3);
});
it('should return undefined if the argument is undefined', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('count(undefined)'), undefined);
});
it('should count arrays containing undefined elements', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('count([1, undefined, 3])'), 3);
});
});

describe('join(sep, array)', function () {
it('should return an empty string on an empty array', function () {
const parser = new Parser();
Expand Down
Loading