diff --git a/src/json-expression/__tests__/jsonExpressionUnitTests.ts b/src/json-expression/__tests__/jsonExpressionUnitTests.ts index 7c3bb3a8fa..0377665706 100644 --- a/src/json-expression/__tests__/jsonExpressionUnitTests.ts +++ b/src/json-expression/__tests__/jsonExpressionUnitTests.ts @@ -1640,6 +1640,44 @@ export const jsonExpressionUnitTests = ( }); }); + describe('push', () => { + test('can push static values into static array', () => { + const arr: unknown[] = []; + check(['push', [arr], 1], [1]); + check(['push', [arr], 1, 2, 3], [1, 2, 3]); + check(['push', [arr], 1, '2', true, [[]]], [1, '2', true, []]); + check(['push', [[1]], 2, 3], [1, 2, 3]); + }); + + test('can push static values into array', () => { + check(['push', ['$', '/arr'], 1], [1], {arr: []}); + check(['push', ['$', '/arr'], 1, 2, 3], [1, 2, 3], {arr: []}); + check(['push', ['$', '/arr'], 1, 2, 3], [0, 1, 2, 3], {arr: [0]}); + }); + + test('can push values into static array', () => { + check(['push', [[]], ['$', '/val'], 1], [0, 1], {val: 0}); + }); + + test('can push values into array', () => { + check(['push', ['$', '/arr'], ['$', '/val'], '2'], [0, 1, '2'], {arr: [0], val: 1}); + }); + + test('concatenates empty arrays', () => { + check(['push', [[1]], [[]]], [1, []]); + check(['push', [[]], [[]]], [[]]); + }); + + test('throws on invalid operand count', () => { + expect(() => check(['push', [[]]] as any, false)).toThrowErrorMatchingInlineSnapshot( + `""push" operator expects at least two operands."`, + ); + expect(() => check(['push', []] as any, false)).toThrowErrorMatchingInlineSnapshot( + `""push" operator expects at least two operands."`, + ); + }); + }); + describe('head', () => { test('returns first two elements', () => { check(['head', [[1, 2, 3]], 2], [1, 2]); diff --git a/src/json-expression/codegen.ts b/src/json-expression/codegen.ts index 1944a16381..a20da09078 100644 --- a/src/json-expression/codegen.ts +++ b/src/json-expression/codegen.ts @@ -73,6 +73,7 @@ export class JsonExpressionCodegen { link: this.linkOperandDeps, const: this.operatorConst, subExpression: this.subExpression, + var: (value: string) => this.codegen.var(value), }; return codegen(ctx); } diff --git a/src/json-expression/index.ts b/src/json-expression/index.ts index 200b62f759..088ff68938 100644 --- a/src/json-expression/index.ts +++ b/src/json-expression/index.ts @@ -1,3 +1,4 @@ export * from './types'; export * from './evaluate'; export * from './codegen'; +export * from './Vars'; diff --git a/src/json-expression/operators/array.ts b/src/json-expression/operators/array.ts index 589bbcf49b..0d9c3f6628 100644 --- a/src/json-expression/operators/array.ts +++ b/src/json-expression/operators/array.ts @@ -3,6 +3,7 @@ import {Expression, ExpressionResult, Literal} from '../codegen-steps'; import {$$deepEqual} from '../../json-equal/$$deepEqual'; import type * as types from '../types'; import {Vars} from '../Vars'; +import {clone} from '../../json-clone'; const createSubExpressionOperator = ( name: N, @@ -51,6 +52,35 @@ export const arrayOperators: types.OperatorDefinition[] = [ }, ] as types.OperatorDefinition, + [ + 'push', + [], + -1, + (expr: types.ExprPush, ctx) => { + const operand1 = ctx.eval(expr[1], ctx); + const arr = clone(util.asArr(operand1)); + for (let i = 2; i < expr.length; i++) arr.push(ctx.eval(expr[i], ctx)); + return arr; + }, + (ctx: types.OperatorCodegenCtx): ExpressionResult => { + const arrOperand = ctx.operands[0]; + let arr: Literal | Expression; + if (arrOperand instanceof Literal) { + arr = new Literal(clone(util.asArr(arrOperand.val))); + } else { + ctx.link(util.asArr, 'asArr'); + arr = new Expression(`asArr(${arrOperand})`); + } + const rArr = ctx.var('' + arr); + const pushes: string[] = []; + for (let i = 1; i < ctx.operands.length; i++) { + const operand = ctx.operands[i]; + pushes.push(`(${rArr}.push(${operand}))`); + } + return new Expression(`(${pushes.join(',')},${rArr})`); + }, + ] as types.OperatorDefinition, + [ 'head', [], diff --git a/src/json-expression/types.ts b/src/json-expression/types.ts index 0752cc5589..c3d88179fa 100644 --- a/src/json-expression/types.ts +++ b/src/json-expression/types.ts @@ -200,6 +200,7 @@ export type ExprF64 = BinaryExpression<'f64'>; // Array expressions export type ArrayExpression = | ExprConcat + | ExprPush | ExprHead | ExprSort | ExprReverse @@ -213,6 +214,7 @@ export type ArrayExpression = | ExprReduce; export type ExprConcat = VariadicExpression<'concat' | '++'>; +export type ExprPush = VariadicExpression<'push'>; export type ExprHead = BinaryExpression<'head'>; export type ExprSort = UnaryExpression<'sort'>; export type ExprReverse = UnaryExpression<'reverse'>; @@ -320,6 +322,7 @@ export interface OperatorCodegenCtx extends JsonExpression link: (value: unknown, name?: string) => string; const: (js: JavaScript) => string; subExpression: (expr: Expression) => (ctx: JsonExpressionExecutionContext) => unknown; + var: (name: string) => string; } export type OperatorMap = Map>;