node-opcua-numeric-range 2.55.0 → 2.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,775 +1,778 @@
1
- /**
2
- * @module node-opcua-numeric-range
3
- */
4
- import { assert } from "node-opcua-assert";
5
-
6
- import { decodeString, encodeString, UAString, UInt8 } from "node-opcua-basic-types";
7
- import { BinaryStream, OutputBinaryStream } from "node-opcua-binary-stream";
8
- import { registerBasicType } from "node-opcua-factory";
9
- import { StatusCode, StatusCodes } from "node-opcua-status-code";
10
- import { debuglog } from "util";
11
-
12
- // OPC.UA Part 4 7.21 Numerical Range
13
- // The syntax for the string contains one of the following two constructs. The first construct is the string
14
- // representation of an individual integer. For example, '6' is valid, but '6.0' and '3.2' are not. The
15
- // minimum and maximum values that can be expressed are defined by the use of this parameter and
16
- // not by this parameter type definition. The second construct is a range represented by two integers
17
- // separated by the colon (':') character. The first integer shall always have a lower value than the
18
- // second. For example, '5:7' is valid, while '7:5' and '5:5' are not. The minimum and maximum values
19
- // that can be expressed by these integers are defined by the use of this parameter , and not by this
20
- // parameter type definition. No other characters, including white - space characters, are permitted.
21
- // Multi- dimensional arrays can be indexed by specifying a range for each dimension separated by a ','.
22
- //
23
- // For example, a 2x2 block in a 4x4 matrix could be selected with the range '1:2,0:1'. A single element
24
- // in a multi - dimensional array can be selected by specifying a single number instead of a range.
25
- // For example, '1,1' specifies selects the [1,1] element in a two dimensional array.
26
- // Dimensions are specified in the order that they appear in the ArrayDimensions Attribute.
27
- // All dimensions shall be specified for a NumericRange to be valid.
28
- //
29
- // All indexes start with 0. The maximum value for any index is one less than the length of the
30
- // dimension.
31
-
32
- const NUMERIC_RANGE_EMPTY_STRING = "NumericRange:<Empty>";
33
-
34
- // BNF of NumericRange
35
- // The following BNF describes the syntax of the NumericRange parameter type.
36
- // <numeric-range> ::= <dimension> [',' <dimension>]
37
- // <dimension> ::= <index> [':' <index>]
38
- // <index> ::= <digit> [<digit>]
39
- // <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' |9'
40
- //
41
- // tslint:disable:object-literal-shorthand
42
- // tslint:disable:only-arrow-functions
43
- export const schemaNumericRange = {
44
- name: "NumericRange",
45
- subType: "UAString",
46
-
47
- defaultValue: () => {
48
- return new NumericRange();
49
- },
50
- encode: encodeNumericRange,
51
-
52
- decode: decodeNumericRange,
53
-
54
- random: (): NumericRange => {
55
- function r() {
56
- return Math.ceil(Math.random() * 100);
57
- }
58
-
59
- const start = r();
60
- const end = start + r();
61
- return new NumericRange(start, end);
62
- },
63
-
64
- coerce: coerceNumericRange
65
- };
66
-
67
- registerBasicType(schemaNumericRange);
68
-
69
- export enum NumericRangeType {
70
- Empty = 0,
71
- SingleValue = 1,
72
- ArrayRange = 2,
73
- MatrixRange = 3,
74
- InvalidRange = 4
75
- }
76
-
77
- // new Enum(["Empty", "SingleValue", "ArrayRange", "MatrixRange", "InvalidRange"]);
78
-
79
- const regexNumericRange = /^[0-9:,]*$/;
80
-
81
- function _valid_range(low: number, high: number): boolean {
82
- return !(low >= high || low < 0 || high < 0);
83
- }
84
-
85
- type NumericalRangeValueType = null | number | string | number[] | number[][];
86
-
87
- export interface NumericalRangeSingleValue {
88
- type: NumericRangeType.SingleValue;
89
- value: number;
90
- }
91
-
92
- export interface NumericalRangeArrayRange {
93
- type: NumericRangeType.ArrayRange;
94
- value: number[];
95
- }
96
-
97
- export interface NumericalRangeMatrixRange {
98
- type: NumericRangeType.MatrixRange;
99
- value: number[][];
100
- }
101
-
102
- export interface NumericalRangeEmpty {
103
- type: NumericRangeType.Empty;
104
- value: null;
105
- }
106
-
107
- export interface NumericalRangeInvalid {
108
- type: NumericRangeType.InvalidRange;
109
- value: string;
110
- }
111
-
112
- export type NumericalRange0 =
113
- | NumericalRangeSingleValue
114
- | NumericalRangeArrayRange
115
- | NumericalRangeMatrixRange
116
- | NumericalRangeEmpty
117
- | NumericalRangeInvalid;
118
-
119
- export interface NumericalRange1 {
120
- type: NumericRangeType;
121
- value: NumericalRangeValueType;
122
- }
123
-
124
- function construct_numeric_range_bit_from_string(str: string): NumericalRange0 {
125
- const values = str.split(":");
126
-
127
- if (values.length === 1) {
128
- return {
129
- type: NumericRangeType.SingleValue,
130
- value: parseInt(values[0], 10)
131
- };
132
- } else if (values.length === 2) {
133
- const array = values.map((a) => parseInt(a, 10));
134
-
135
- if (!_valid_range(array[0], array[1])) {
136
- return {
137
- type: NumericRangeType.InvalidRange,
138
- value: str
139
- };
140
- }
141
- return {
142
- type: NumericRangeType.ArrayRange,
143
- value: array
144
- };
145
- } else {
146
- return {
147
- type: NumericRangeType.InvalidRange,
148
- value: str
149
- };
150
- }
151
- }
152
-
153
- function _normalize(e: NumericalRange1): number | number[] {
154
- if (e.type === NumericRangeType.SingleValue) {
155
- const ee = e as NumericalRangeSingleValue;
156
- return [ee.value, ee.value];
157
- }
158
- return e.value as number;
159
- }
160
-
161
- function construct_numeric_range_from_string(str: string): NumericalRange0 {
162
- if (!regexNumericRange.test(str)) {
163
- return {
164
- type: NumericRangeType.InvalidRange,
165
- value: str
166
- };
167
- }
168
- /* detect multi dim range*/
169
- const values = str.split(",");
170
-
171
- if (values.length === 1) {
172
- return construct_numeric_range_bit_from_string(values[0]);
173
- } else if (values.length === 2) {
174
- const elements = values.map(construct_numeric_range_bit_from_string);
175
- let rowRange: any = elements[0];
176
- let colRange: any = elements[1];
177
- if (rowRange.type === NumericRangeType.InvalidRange || colRange.type === NumericRangeType.InvalidRange) {
178
- return { type: NumericRangeType.InvalidRange, value: str };
179
- }
180
- rowRange = _normalize(rowRange);
181
- colRange = _normalize(colRange);
182
- return {
183
- type: NumericRangeType.MatrixRange,
184
- value: [rowRange, colRange]
185
- };
186
- } else {
187
- // not supported yet
188
- return { type: NumericRangeType.InvalidRange, value: str };
189
- }
190
- }
191
-
192
- function construct_from_string(value: string): NumericalRange0 {
193
- return construct_numeric_range_from_string(value);
194
- }
195
-
196
- function _set_single_value(value: number | null): NumericalRange0 {
197
- if (value === null || value < 0 || !isFinite(value)) {
198
- return {
199
- type: NumericRangeType.InvalidRange,
200
- value: "" + value?.toString()
201
- };
202
- } else {
203
- return {
204
- type: NumericRangeType.SingleValue,
205
- value: value
206
- };
207
- }
208
- }
209
-
210
- function _check_range(numericalRange: NumericalRange0) {
211
- switch (numericalRange.type) {
212
- case NumericRangeType.ArrayRange:
213
- return _valid_range(numericalRange.value[0], numericalRange.value[1]);
214
- }
215
- // istanbul ignore next
216
- throw new Error("unsupported case");
217
- }
218
-
219
- function _set_range_value(low: number, high: number): NumericalRangeSingleValue | NumericalRangeArrayRange | NumericalRangeInvalid {
220
- if (low === high) {
221
- return {
222
- type: NumericRangeType.SingleValue,
223
- value: low
224
- };
225
- }
226
- const numericalRange: NumericalRangeArrayRange = {
227
- type: NumericRangeType.ArrayRange,
228
- value: [low, high]
229
- };
230
- if (!_check_range(numericalRange as NumericalRangeArrayRange)) {
231
- return {
232
- type: NumericRangeType.InvalidRange,
233
- value: ""
234
- };
235
- }
236
- return numericalRange;
237
- }
238
-
239
- function construct_from_values(value: number, secondValue?: number): NumericalRange0 {
240
- if (secondValue === undefined) {
241
- return _set_single_value(value);
242
- } else {
243
- if (!isFinite(secondValue)) {
244
- throw new Error(" invalid second argument, expecting a number");
245
- }
246
- return _set_range_value(value, secondValue);
247
- }
248
- }
249
-
250
- function _construct_from_array(value: number[], value2?: any): NumericalRange0 {
251
- assert(value.length === 2);
252
-
253
- // istanbul ignore next
254
- if (!isFinite(value[0]) || !isFinite(value[1])) {
255
- return { type: NumericRangeType.InvalidRange, value: "" + value };
256
- }
257
- let range1 = _set_range_value(value[0], value[1]);
258
- if (!value2) {
259
- return range1;
260
- }
261
- // we have a matrix
262
- const nr2 = new NumericRange(value2);
263
- // istanbul ignore next
264
- if (
265
- nr2.type === NumericRangeType.InvalidRange ||
266
- nr2.type === NumericRangeType.MatrixRange ||
267
- nr2.type === NumericRangeType.Empty
268
- ) {
269
- return { type: NumericRangeType.InvalidRange, value: "" + value };
270
- }
271
- if (range1.type === NumericRangeType.SingleValue) {
272
- range1 = {
273
- type: NumericRangeType.ArrayRange,
274
- value: [range1.value, range1.value]
275
- };
276
- }
277
- if (nr2.type === NumericRangeType.SingleValue) {
278
- nr2.type = NumericRangeType.ArrayRange;
279
- nr2.value = [nr2.value as number, nr2.value as number];
280
- }
281
-
282
- // istanbul ignore next
283
- return {
284
- type: NumericRangeType.MatrixRange,
285
- value: [range1.value as number[], nr2.value as number[]]
286
- };
287
- }
288
-
289
- export class NumericRange implements NumericalRange1 {
290
- public static coerce = coerceNumericRange;
291
-
292
- public static schema = schemaNumericRange;
293
- // tslint:disable:variable-name
294
- public static NumericRangeType = NumericRangeType;
295
-
296
- public static readonly empty = new NumericRange() as NumericalRange0;
297
-
298
- public static overlap(nr1?: NumericalRange0, nr2?: NumericalRange0) {
299
- nr1 = nr1 || NumericRange.empty;
300
- nr2 = nr2 || NumericRange.empty;
301
-
302
- if (NumericRangeType.Empty === nr1.type || NumericRangeType.Empty === nr2.type) {
303
- return true;
304
- }
305
- if (NumericRangeType.SingleValue === nr1.type && NumericRangeType.SingleValue === nr2.type) {
306
- return nr1.value === nr2.value;
307
- }
308
- if (NumericRangeType.ArrayRange === nr1.type && NumericRangeType.ArrayRange === nr2.type) {
309
- // +-----+ +------+ +---+ +------+
310
- // +----+ +---+ +--------+ +---+
311
- const l1 = nr1.value[0];
312
- const h1 = nr1.value[1];
313
- const l2 = nr2.value[0];
314
- const h2 = nr2.value[1];
315
- return _overlap(l1, h1, l2, h2);
316
- }
317
- // istanbul ignore next
318
- assert(false, "NumericalRange#overlap : case not implemented yet "); // TODO
319
- // istanbul ignore next
320
- return false;
321
- }
322
-
323
- public type: NumericRangeType;
324
- public value: NumericalRangeValueType;
325
-
326
- constructor();
327
- // tslint:disable-next-line: unified-signatures
328
- constructor(value: string | null);
329
- // tslint:disable-next-line: unified-signatures
330
- constructor(value: number, secondValue?: number);
331
- // tslint:disable-next-line: unified-signatures
332
- constructor(value: number[]);
333
- // tslint:disable-next-line: unified-signatures
334
- constructor(value: number[], secondValue: number[]);
335
- constructor(value?: null | string | number | number[], secondValue?: number | number[]) {
336
- this.type = NumericRangeType.InvalidRange;
337
- this.value = null;
338
-
339
- assert(!value || !(value instanceof NumericRange), "use coerce to create a NumericRange");
340
- assert(!secondValue || typeof secondValue === "number" || Array.isArray(secondValue));
341
- if (typeof value === "string") {
342
- const a = construct_from_string(value as string);
343
- this.type = a.type;
344
- this.value = a.value;
345
- } else if (
346
- typeof value === "number" &&
347
- isFinite(value) &&
348
- (secondValue === undefined || (typeof secondValue === "number" && isFinite(secondValue)))
349
- ) {
350
- const a = construct_from_values(value, secondValue);
351
- this.type = a.type;
352
- this.value = a.value;
353
- } else if (Array.isArray(value)) {
354
- const a = _construct_from_array(value, secondValue);
355
- this.type = a.type;
356
- this.value = a.value;
357
- } else {
358
- this.value = "<invalid>";
359
- this.type = NumericRangeType.Empty;
360
- }
361
-
362
- // xx assert((this.type !== NumericRangeType.ArrayRange) || Array.isArray(this.value));
363
- }
364
-
365
- public isValid(): boolean {
366
- if (this.type === NumericRangeType.ArrayRange) {
367
- const value = this.value as number[];
368
- if (value[0] < 0 || value[1] < 0) {
369
- return false;
370
- }
371
- }
372
- if (this.type === NumericRangeType.SingleValue) {
373
- const value = this.value as number;
374
- // istanbul ignore next
375
- if (value < 0) {
376
- return false;
377
- }
378
- }
379
- return this.type !== NumericRangeType.InvalidRange;
380
- }
381
-
382
- public isEmpty(): boolean {
383
- return this.type === NumericRangeType.Empty;
384
- }
385
-
386
- public isDefined(): boolean {
387
- return this.type !== NumericRangeType.Empty && this.type !== NumericRangeType.InvalidRange;
388
- }
389
-
390
- public toString(): string {
391
- function array_range_to_string(values: number[]): string {
392
- assert(Array.isArray(values));
393
- if (values.length === 2 && values[0] === values[1]) {
394
- return values[0].toString();
395
- }
396
- return values.map((value) => value.toString(10)).join(":");
397
- }
398
-
399
- function matrix_range_to_string(values: any) {
400
- return values
401
- .map((value: any) => {
402
- return Array.isArray(value) ? array_range_to_string(value) : value.toString(10);
403
- })
404
- .join(",");
405
- }
406
-
407
- switch (this.type) {
408
- case NumericRangeType.SingleValue:
409
- return (this.value as any).toString(10);
410
-
411
- case NumericRangeType.ArrayRange:
412
- return array_range_to_string(this.value as number[]);
413
-
414
- case NumericRangeType.Empty:
415
- return NUMERIC_RANGE_EMPTY_STRING;
416
-
417
- case NumericRangeType.MatrixRange:
418
- return matrix_range_to_string(this.value);
419
-
420
- default:
421
- assert(this.type === NumericRangeType.InvalidRange);
422
- return "NumericRange:<Invalid>";
423
- }
424
- }
425
-
426
- public toJSON(): string {
427
- return this.toString();
428
- }
429
-
430
- public toEncodeableString(): UAString {
431
- switch (this.type) {
432
- case NumericRangeType.SingleValue:
433
- case NumericRangeType.ArrayRange:
434
- case NumericRangeType.MatrixRange:
435
- return this.toString();
436
- case NumericRangeType.InvalidRange:
437
- // istanbul ignore next
438
- if (!(typeof this.value === "string")) {
439
- throw new Error("Internal Error");
440
- }
441
- return this.value; // value contains the original strings which was detected invalid
442
- default:
443
- return null;
444
- }
445
- }
446
-
447
- /**
448
- * @method extract_values
449
- * @param array flat array containing values or string
450
- * @param dimensions: of the matrix if data is a matrix
451
- * @return {*}
452
- */
453
- public extract_values<U, T extends ArrayLike<U>>(array: T, dimensions?: number[]): ExtractResult<T> {
454
- const self = this as NumericalRange0;
455
-
456
- if (!array) {
457
- return {
458
- array,
459
- statusCode: this.type === NumericRangeType.Empty ? StatusCodes.Good : StatusCodes.BadIndexRangeNoData
460
- };
461
- }
462
-
463
- let index;
464
- let low_index;
465
- let high_index;
466
- let rowRange;
467
- let colRange;
468
- switch (self.type) {
469
- case NumericRangeType.Empty:
470
- return extract_empty(array, dimensions);
471
-
472
- case NumericRangeType.SingleValue:
473
- index = self.value;
474
- return extract_single_value(array, index);
475
-
476
- case NumericRangeType.ArrayRange:
477
- low_index = self.value[0];
478
- high_index = self.value[1];
479
- return extract_array_range(array, low_index, high_index);
480
-
481
- case NumericRangeType.MatrixRange:
482
- rowRange = self.value[0];
483
- colRange = self.value[1];
484
- return extract_matrix_range(array, rowRange, colRange, dimensions);
485
-
486
- default:
487
- return { statusCode: StatusCodes.BadIndexRangeInvalid };
488
- }
489
- }
490
-
491
- public set_values_matrix(sourceToAlter: { matrix: Buffer | []; dimensions: number[] }, newMatrix: Buffer | []) {
492
- const { matrix, dimensions } = sourceToAlter;
493
- const self = this as NumericalRange0;
494
- assert(dimensions, "expecting valid dimensions here");
495
- if (self.type !== NumericRangeType.MatrixRange) {
496
- // istanbul ignore next
497
- return { matrix, statusCode: StatusCodes.BadTypeMismatch };
498
- }
499
-
500
- assert(dimensions.length === 2);
501
- const nbRows = dimensions[0];
502
- const nbCols = dimensions[1];
503
- assert(sourceToAlter.matrix.length === nbRows * nbCols);
504
- const [rowStart, rowEnd] = self.value[0];
505
- const [colStart, colEnd] = self.value[1];
506
-
507
- const nbRowInNew = rowEnd - rowStart + 1;
508
- const nbColInNew = colEnd - colStart + 1;
509
- if (nbRowInNew * nbColInNew !== newMatrix.length) {
510
- return { matrix, statusCode: StatusCodes.BadTypeMismatch };
511
- }
512
- // check if the sub-matrix is in th range of the initial matrix
513
- if (rowEnd >= nbRows || colEnd >= nbCols) {
514
- // debugLog("out of band range => ", { rowEnd, nbRows, colEnd, nbCols });
515
- return { matrix, statusCode: StatusCodes.BadTypeMismatch };
516
- }
517
- for (let row = rowStart; row <= rowEnd; row++) {
518
- const ri = row - rowStart;
519
- for (let col = colStart; col <= colEnd; col++) {
520
- const ci = col - colStart;
521
- matrix[row * nbCols + col] = newMatrix[ri * nbColInNew + ci];
522
- }
523
- }
524
- return {
525
- matrix,
526
- statusCode: StatusCodes.Good
527
- };
528
- }
529
- public set_values(arrayToAlter: Buffer | [], newValues: Buffer | []) {
530
- assert_array_or_buffer(arrayToAlter);
531
- assert_array_or_buffer(newValues);
532
-
533
- const self = this as NumericalRange0;
534
-
535
- let low_index;
536
- let high_index;
537
- switch (self.type) {
538
- case NumericRangeType.Empty:
539
- low_index = 0;
540
- high_index = arrayToAlter.length - 1;
541
- break;
542
- case NumericRangeType.SingleValue:
543
- low_index = self.value;
544
- high_index = self.value;
545
- break;
546
- case NumericRangeType.ArrayRange:
547
- low_index = self.value[0];
548
- high_index = self.value[1];
549
- break;
550
- case NumericRangeType.MatrixRange:
551
- // for the time being MatrixRange is not supported
552
- return { array: arrayToAlter, statusCode: StatusCodes.BadIndexRangeNoData };
553
- default:
554
- return { array: [], statusCode: StatusCodes.BadIndexRangeInvalid };
555
- }
556
-
557
- if (high_index >= arrayToAlter.length || low_index >= arrayToAlter.length) {
558
- return { array: [], statusCode: StatusCodes.BadIndexRangeNoData };
559
- }
560
- // istanbul ignore next
561
- if (this.type !== NumericRangeType.Empty && newValues.length !== high_index - low_index + 1) {
562
- return { array: [], statusCode: StatusCodes.BadIndexRangeInvalid };
563
- }
564
- const insertInPlace = Array.isArray(arrayToAlter)
565
- ? insertInPlaceStandardArray
566
- : arrayToAlter instanceof Buffer
567
- ? insertInPlaceBuffer
568
- : insertInPlaceTypedArray;
569
- return {
570
- array: insertInPlace(arrayToAlter, low_index, high_index, newValues),
571
- statusCode: StatusCodes.Good
572
- };
573
- }
574
- }
575
-
576
- function slice<U, T extends ArrayLike<U>>(arr: T, start: number, end: number): T {
577
- if (start === 0 && end === arr.length) {
578
- return arr;
579
- }
580
-
581
- let res;
582
- if ((arr as any).buffer instanceof ArrayBuffer) {
583
- res = (arr as any).subarray(start, end);
584
- } else {
585
- assert(typeof (arr as any).slice === "function");
586
- assert(arr instanceof Buffer || arr instanceof Array || typeof arr === "string");
587
- res = (arr as any).slice(start, end);
588
- }
589
- if (res instanceof Uint8Array && arr instanceof Buffer) {
590
- // note in io-js 3.00 onward standard Buffer are implemented differently and
591
- // provides a buffer member and a subarray method, in fact in io-js 3.0
592
- // it seems that Buffer acts as a Uint8Array. in this very special case
593
- // we need to make sure that we end up with a Buffer object and not a Uint8Array.
594
- res = Buffer.from(res);
595
- }
596
- return res;
597
- }
598
-
599
- export interface ExtractResult<T> {
600
- array?: T;
601
- statusCode: StatusCode;
602
- dimensions?: number[];
603
- }
604
-
605
- function extract_empty<U, T extends ArrayLike<U>>(array: T, dimensions: any): ExtractResult<T> {
606
- return {
607
- array: slice(array, 0, array.length),
608
- dimensions,
609
- statusCode: StatusCodes.Good
610
- };
611
- }
612
-
613
- function extract_single_value<U, T extends ArrayLike<U>>(array: T, index: number): ExtractResult<T> {
614
- if (index >= array.length) {
615
- if (typeof array === "string") {
616
- return { array: ("" as any) as T, statusCode: StatusCodes.BadIndexRangeNoData };
617
- }
618
- return { array: ([] as any) as T, statusCode: StatusCodes.BadIndexRangeNoData };
619
- }
620
- return {
621
- array: slice(array, index, index + 1),
622
- statusCode: StatusCodes.Good
623
- };
624
- }
625
-
626
- function extract_array_range<U, T extends ArrayLike<U>>(array: T, low_index: number, high_index: number): ExtractResult<T> {
627
- assert(isFinite(low_index) && isFinite(high_index));
628
- assert(low_index >= 0);
629
- assert(low_index <= high_index);
630
- if (low_index >= array.length) {
631
- if (typeof array === "string") {
632
- return { array: ("" as any) as T, statusCode: StatusCodes.BadIndexRangeNoData };
633
- }
634
- return { array: ([] as any) as T, statusCode: StatusCodes.BadIndexRangeNoData };
635
- }
636
- // clamp high index
637
- high_index = Math.min(high_index, array.length - 1);
638
-
639
- return {
640
- array: slice(array, low_index, high_index + 1),
641
- statusCode: StatusCodes.Good
642
- };
643
- }
644
-
645
- function isArrayLike(value: any): boolean {
646
- return typeof value.length === "number" || value.hasOwnProperty("length");
647
- }
648
-
649
- function extract_matrix_range<U, T extends ArrayLike<U>>(
650
- array: T,
651
- rowRange: number[],
652
- colRange: number[],
653
- dimension?: number[]
654
- ): ExtractResult<T> {
655
- assert(Array.isArray(rowRange) && Array.isArray(colRange));
656
-
657
- if (array.length === 0) {
658
- return {
659
- array: ([] as any) as T,
660
- statusCode: StatusCodes.BadIndexRangeNoData
661
- };
662
- }
663
- if (isArrayLike((array as any)[0]) && !dimension) {
664
- // like extracting data from a one dimensional array of strings or byteStrings...
665
- const result = extract_array_range(array, rowRange[0], rowRange[1]);
666
- for (let i = 0; i < result.array!.length; i++) {
667
- const e = (result.array! as any)[i];
668
- (result.array as any)[i] = extract_array_range(e, colRange[0], colRange[1]).array;
669
- }
670
- return result;
671
- }
672
- if (!dimension) {
673
- return {
674
- array: ([] as any) as T,
675
- statusCode: StatusCodes.BadIndexRangeNoData
676
- };
677
- }
678
-
679
- assert(dimension, "expecting dimension to know the shape of the matrix represented by the flat array");
680
-
681
- //
682
- const rowLow = rowRange[0];
683
- const rowHigh = rowRange[1];
684
- const colLow = colRange[0];
685
- const colHigh = colRange[1];
686
-
687
- const nbRow = dimension[0];
688
- const nbCol = dimension[1];
689
-
690
- const nbRowDest = rowHigh - rowLow + 1;
691
- const nbColDest = colHigh - colLow + 1;
692
-
693
- // construct an array of the same type with the appropriate length to
694
- // store the extracted matrix.
695
- const tmp = new (array as any).constructor(nbColDest * nbRowDest);
696
-
697
- let row;
698
- let col;
699
- let r;
700
- let c;
701
- r = 0;
702
- for (row = rowLow; row <= rowHigh; row++) {
703
- c = 0;
704
- for (col = colLow; col <= colHigh; col++) {
705
- const srcIndex = row * nbCol + col;
706
- const destIndex = r * nbColDest + c;
707
- tmp[destIndex] = (array as any)[srcIndex];
708
- c++;
709
- }
710
- r += 1;
711
- }
712
- return {
713
- array: tmp,
714
- dimensions: [nbRowDest, nbColDest],
715
- statusCode: StatusCodes.Good
716
- };
717
- }
718
-
719
- function assert_array_or_buffer(array: any) {
720
- assert(Array.isArray(array) || array.buffer instanceof ArrayBuffer || array instanceof Buffer);
721
- }
722
-
723
- function insertInPlaceStandardArray(arrayToAlter: any, low: number, high: number, newValues: any): any {
724
- const args = [low, high - low + 1].concat(newValues);
725
- arrayToAlter.splice.apply(arrayToAlter, args);
726
- return arrayToAlter;
727
- }
728
-
729
- function insertInPlaceTypedArray(arrayToAlter: any, low: number, high: number, newValues: any): any {
730
- if (low === 0 && high === arrayToAlter.length - 1) {
731
- return new arrayToAlter.constructor(newValues);
732
- }
733
- assert(newValues.length === high - low + 1);
734
- arrayToAlter.subarray(low, high + 1).set(newValues);
735
- return arrayToAlter;
736
- }
737
-
738
- function insertInPlaceBuffer(bufferToAlter: any, low: number, high: number, newValues: any): any {
739
- if (low === 0 && high === bufferToAlter.length - 1) {
740
- return Buffer.from(newValues);
741
- }
742
- assert(newValues.length === high - low + 1);
743
- for (let i = 0; i < newValues.length; i++) {
744
- bufferToAlter[i + low] = newValues[i];
745
- }
746
- return bufferToAlter;
747
- }
748
-
749
- function _overlap(l1: number, h1: number, l2: number, h2: number): boolean {
750
- return Math.max(l1, l2) <= Math.min(h1, h2);
751
- }
752
-
753
- export function encodeNumericRange(numericRange: NumericRange, stream: OutputBinaryStream) {
754
- assert(numericRange instanceof NumericRange);
755
- encodeString(numericRange.toEncodeableString(), stream);
756
- }
757
-
758
- export function decodeNumericRange(stream: BinaryStream, _value?: NumericRange): NumericRange {
759
- const str = decodeString(stream)!;
760
- return new NumericRange(str);
761
- }
762
-
763
- function coerceNumericRange(value: any | string | NumericRange | null | number[]): NumericRange {
764
- if (value instanceof NumericRange) {
765
- return value;
766
- }
767
- if (value === null || value === undefined) {
768
- return new NumericRange();
769
- }
770
- if (value === NUMERIC_RANGE_EMPTY_STRING) {
771
- return new NumericRange();
772
- }
773
- assert(typeof value === "string" || Array.isArray(value));
774
- return new NumericRange(value);
775
- }
1
+ /**
2
+ * @module node-opcua-numeric-range
3
+ */
4
+ import { debuglog } from "util";
5
+ import { assert } from "node-opcua-assert";
6
+
7
+ import { decodeString, encodeString, UAString, UInt8 } from "node-opcua-basic-types";
8
+ import { BinaryStream, OutputBinaryStream } from "node-opcua-binary-stream";
9
+ import { registerBasicType } from "node-opcua-factory";
10
+ import { StatusCode, StatusCodes } from "node-opcua-status-code";
11
+
12
+ // OPC.UA Part 4 7.21 Numerical Range
13
+ // The syntax for the string contains one of the following two constructs. The first construct is the string
14
+ // representation of an individual integer. For example, '6' is valid, but '6.0' and '3.2' are not. The
15
+ // minimum and maximum values that can be expressed are defined by the use of this parameter and
16
+ // not by this parameter type definition. The second construct is a range represented by two integers
17
+ // separated by the colon (':') character. The first integer shall always have a lower value than the
18
+ // second. For example, '5:7' is valid, while '7:5' and '5:5' are not. The minimum and maximum values
19
+ // that can be expressed by these integers are defined by the use of this parameter , and not by this
20
+ // parameter type definition. No other characters, including white - space characters, are permitted.
21
+ // Multi- dimensional arrays can be indexed by specifying a range for each dimension separated by a ','.
22
+ //
23
+ // For example, a 2x2 block in a 4x4 matrix could be selected with the range '1:2,0:1'. A single element
24
+ // in a multi - dimensional array can be selected by specifying a single number instead of a range.
25
+ // For example, '1,1' specifies selects the [1,1] element in a two dimensional array.
26
+ // Dimensions are specified in the order that they appear in the ArrayDimensions Attribute.
27
+ // All dimensions shall be specified for a NumericRange to be valid.
28
+ //
29
+ // All indexes start with 0. The maximum value for any index is one less than the length of the
30
+ // dimension.
31
+
32
+ const NUMERIC_RANGE_EMPTY_STRING = "NumericRange:<Empty>";
33
+
34
+ // BNF of NumericRange
35
+ // The following BNF describes the syntax of the NumericRange parameter type.
36
+ // <numeric-range> ::= <dimension> [',' <dimension>]
37
+ // <dimension> ::= <index> [':' <index>]
38
+ // <index> ::= <digit> [<digit>]
39
+ // <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' |9'
40
+ //
41
+ // tslint:disable:object-literal-shorthand
42
+ // tslint:disable:only-arrow-functions
43
+ export const schemaNumericRange = {
44
+ name: "NumericRange",
45
+ subType: "UAString",
46
+
47
+ defaultValue: (): NumericRange => {
48
+ return new NumericRange();
49
+ },
50
+ encode: encodeNumericRange,
51
+
52
+ decode: decodeNumericRange,
53
+
54
+ random: (): NumericRange => {
55
+ function r() {
56
+ return Math.ceil(Math.random() * 100);
57
+ }
58
+
59
+ const start = r();
60
+ const end = start + r();
61
+ return new NumericRange(start, end);
62
+ },
63
+
64
+ coerce: coerceNumericRange
65
+ };
66
+
67
+ registerBasicType(schemaNumericRange);
68
+
69
+ export enum NumericRangeType {
70
+ Empty = 0,
71
+ SingleValue = 1,
72
+ ArrayRange = 2,
73
+ MatrixRange = 3,
74
+ InvalidRange = 4
75
+ }
76
+
77
+ // new Enum(["Empty", "SingleValue", "ArrayRange", "MatrixRange", "InvalidRange"]);
78
+
79
+ const regexNumericRange = /^[0-9:,]*$/;
80
+
81
+ function _valid_range(low: number, high: number): boolean {
82
+ return !(low >= high || low < 0 || high < 0);
83
+ }
84
+
85
+ type NumericalRangeValueType = null | number | string | number[] | number[][];
86
+
87
+ export interface NumericalRangeSingleValue {
88
+ type: NumericRangeType.SingleValue;
89
+ value: number;
90
+ }
91
+
92
+ export interface NumericalRangeArrayRange {
93
+ type: NumericRangeType.ArrayRange;
94
+ value: number[];
95
+ }
96
+
97
+ export interface NumericalRangeMatrixRange {
98
+ type: NumericRangeType.MatrixRange;
99
+ value: number[][];
100
+ }
101
+
102
+ export interface NumericalRangeEmpty {
103
+ type: NumericRangeType.Empty;
104
+ value: null;
105
+ }
106
+
107
+ export interface NumericalRangeInvalid {
108
+ type: NumericRangeType.InvalidRange;
109
+ value: string;
110
+ }
111
+
112
+ export type NumericalRange0 =
113
+ | NumericalRangeSingleValue
114
+ | NumericalRangeArrayRange
115
+ | NumericalRangeMatrixRange
116
+ | NumericalRangeEmpty
117
+ | NumericalRangeInvalid;
118
+
119
+ export interface NumericalRange1 {
120
+ type: NumericRangeType;
121
+ value: NumericalRangeValueType;
122
+ }
123
+
124
+ function construct_numeric_range_bit_from_string(str: string): NumericalRange0 {
125
+ const values = str.split(":");
126
+
127
+ if (values.length === 1) {
128
+ return {
129
+ type: NumericRangeType.SingleValue,
130
+ value: parseInt(values[0], 10)
131
+ };
132
+ } else if (values.length === 2) {
133
+ const array = values.map((a) => parseInt(a, 10));
134
+
135
+ if (!_valid_range(array[0], array[1])) {
136
+ return {
137
+ type: NumericRangeType.InvalidRange,
138
+ value: str
139
+ };
140
+ }
141
+ return {
142
+ type: NumericRangeType.ArrayRange,
143
+ value: array
144
+ };
145
+ } else {
146
+ return {
147
+ type: NumericRangeType.InvalidRange,
148
+ value: str
149
+ };
150
+ }
151
+ }
152
+
153
+ function _normalize(e: NumericalRange1): number | number[] {
154
+ if (e.type === NumericRangeType.SingleValue) {
155
+ const ee = e as NumericalRangeSingleValue;
156
+ return [ee.value, ee.value];
157
+ }
158
+ return e.value as number;
159
+ }
160
+
161
+ function construct_numeric_range_from_string(str: string): NumericalRange0 {
162
+ if (!regexNumericRange.test(str)) {
163
+ return {
164
+ type: NumericRangeType.InvalidRange,
165
+ value: str
166
+ };
167
+ }
168
+ /* detect multi dim range*/
169
+ const values = str.split(",");
170
+
171
+ if (values.length === 1) {
172
+ return construct_numeric_range_bit_from_string(values[0]);
173
+ } else if (values.length === 2) {
174
+ const elements = values.map(construct_numeric_range_bit_from_string);
175
+ let rowRange: any = elements[0];
176
+ let colRange: any = elements[1];
177
+ if (rowRange.type === NumericRangeType.InvalidRange || colRange.type === NumericRangeType.InvalidRange) {
178
+ return { type: NumericRangeType.InvalidRange, value: str };
179
+ }
180
+ rowRange = _normalize(rowRange);
181
+ colRange = _normalize(colRange);
182
+ return {
183
+ type: NumericRangeType.MatrixRange,
184
+ value: [rowRange, colRange]
185
+ };
186
+ } else {
187
+ // not supported yet
188
+ return { type: NumericRangeType.InvalidRange, value: str };
189
+ }
190
+ }
191
+
192
+ function construct_from_string(value: string): NumericalRange0 {
193
+ return construct_numeric_range_from_string(value);
194
+ }
195
+
196
+ function _set_single_value(value: number | null): NumericalRange0 {
197
+ if (value === null || value < 0 || !isFinite(value)) {
198
+ return {
199
+ type: NumericRangeType.InvalidRange,
200
+ value: "" + value?.toString()
201
+ };
202
+ } else {
203
+ return {
204
+ type: NumericRangeType.SingleValue,
205
+ value: value
206
+ };
207
+ }
208
+ }
209
+
210
+ function _check_range(numericalRange: NumericalRange0) {
211
+ switch (numericalRange.type) {
212
+ case NumericRangeType.ArrayRange:
213
+ return _valid_range(numericalRange.value[0], numericalRange.value[1]);
214
+ }
215
+ // istanbul ignore next
216
+ throw new Error("unsupported case");
217
+ }
218
+
219
+ function _set_range_value(low: number, high: number): NumericalRangeSingleValue | NumericalRangeArrayRange | NumericalRangeInvalid {
220
+ if (low === high) {
221
+ return {
222
+ type: NumericRangeType.SingleValue,
223
+ value: low
224
+ };
225
+ }
226
+ const numericalRange: NumericalRangeArrayRange = {
227
+ type: NumericRangeType.ArrayRange,
228
+ value: [low, high]
229
+ };
230
+ if (!_check_range(numericalRange as NumericalRangeArrayRange)) {
231
+ return {
232
+ type: NumericRangeType.InvalidRange,
233
+ value: ""
234
+ };
235
+ }
236
+ return numericalRange;
237
+ }
238
+
239
+ function construct_from_values(value: number, secondValue?: number): NumericalRange0 {
240
+ if (secondValue === undefined) {
241
+ return _set_single_value(value);
242
+ } else {
243
+ if (!isFinite(secondValue)) {
244
+ throw new Error(" invalid second argument, expecting a number");
245
+ }
246
+ return _set_range_value(value, secondValue);
247
+ }
248
+ }
249
+
250
+ function _construct_from_array(value: number[], value2?: any): NumericalRange0 {
251
+ assert(value.length === 2);
252
+
253
+ // istanbul ignore next
254
+ if (!isFinite(value[0]) || !isFinite(value[1])) {
255
+ return { type: NumericRangeType.InvalidRange, value: "" + value };
256
+ }
257
+ let range1 = _set_range_value(value[0], value[1]);
258
+ if (!value2) {
259
+ return range1;
260
+ }
261
+ // we have a matrix
262
+ const nr2 = new NumericRange(value2);
263
+ // istanbul ignore next
264
+ if (
265
+ nr2.type === NumericRangeType.InvalidRange ||
266
+ nr2.type === NumericRangeType.MatrixRange ||
267
+ nr2.type === NumericRangeType.Empty
268
+ ) {
269
+ return { type: NumericRangeType.InvalidRange, value: "" + value };
270
+ }
271
+ if (range1.type === NumericRangeType.SingleValue) {
272
+ range1 = {
273
+ type: NumericRangeType.ArrayRange,
274
+ value: [range1.value, range1.value]
275
+ };
276
+ }
277
+ if (nr2.type === NumericRangeType.SingleValue) {
278
+ nr2.type = NumericRangeType.ArrayRange;
279
+ nr2.value = [nr2.value as number, nr2.value as number];
280
+ }
281
+
282
+ // istanbul ignore next
283
+ return {
284
+ type: NumericRangeType.MatrixRange,
285
+ value: [range1.value as number[], nr2.value as number[]]
286
+ };
287
+ }
288
+
289
+ export class NumericRange implements NumericalRange1 {
290
+ public static coerce = coerceNumericRange;
291
+
292
+ public static schema = schemaNumericRange;
293
+ // tslint:disable:variable-name
294
+ public static NumericRangeType = NumericRangeType;
295
+
296
+ public static readonly empty = new NumericRange() as NumericalRange0;
297
+
298
+ public static overlap(nr1?: NumericalRange0, nr2?: NumericalRange0): boolean {
299
+ nr1 = nr1 || NumericRange.empty;
300
+ nr2 = nr2 || NumericRange.empty;
301
+
302
+ if (NumericRangeType.Empty === nr1.type || NumericRangeType.Empty === nr2.type) {
303
+ return true;
304
+ }
305
+ if (NumericRangeType.SingleValue === nr1.type && NumericRangeType.SingleValue === nr2.type) {
306
+ return nr1.value === nr2.value;
307
+ }
308
+ if (NumericRangeType.ArrayRange === nr1.type && NumericRangeType.ArrayRange === nr2.type) {
309
+ // +-----+ +------+ +---+ +------+
310
+ // +----+ +---+ +--------+ +---+
311
+ const l1 = nr1.value[0];
312
+ const h1 = nr1.value[1];
313
+ const l2 = nr2.value[0];
314
+ const h2 = nr2.value[1];
315
+ return _overlap(l1, h1, l2, h2);
316
+ }
317
+ // istanbul ignore next
318
+ assert(false, "NumericalRange#overlap : case not implemented yet "); // TODO
319
+ // istanbul ignore next
320
+ return false;
321
+ }
322
+
323
+ public type: NumericRangeType;
324
+ public value: NumericalRangeValueType;
325
+
326
+ constructor();
327
+ // tslint:disable-next-line: unified-signatures
328
+ constructor(value: string | null);
329
+ // tslint:disable-next-line: unified-signatures
330
+ constructor(value: number, secondValue?: number);
331
+ // tslint:disable-next-line: unified-signatures
332
+ constructor(value: number[]);
333
+ // tslint:disable-next-line: unified-signatures
334
+ constructor(value: number[], secondValue: number[]);
335
+ constructor(value?: null | string | number | number[], secondValue?: number | number[]) {
336
+ this.type = NumericRangeType.InvalidRange;
337
+ this.value = null;
338
+
339
+ assert(!value || !(value instanceof NumericRange), "use coerce to create a NumericRange");
340
+ assert(!secondValue || typeof secondValue === "number" || Array.isArray(secondValue));
341
+ if (typeof value === "string") {
342
+ const a = construct_from_string(value as string);
343
+ this.type = a.type;
344
+ this.value = a.value;
345
+ } else if (
346
+ typeof value === "number" &&
347
+ isFinite(value) &&
348
+ (secondValue === undefined || (typeof secondValue === "number" && isFinite(secondValue)))
349
+ ) {
350
+ const a = construct_from_values(value, secondValue);
351
+ this.type = a.type;
352
+ this.value = a.value;
353
+ } else if (Array.isArray(value)) {
354
+ const a = _construct_from_array(value, secondValue);
355
+ this.type = a.type;
356
+ this.value = a.value;
357
+ } else {
358
+ this.value = "<invalid>";
359
+ this.type = NumericRangeType.Empty;
360
+ }
361
+
362
+ // xx assert((this.type !== NumericRangeType.ArrayRange) || Array.isArray(this.value));
363
+ }
364
+
365
+ public isValid(): boolean {
366
+ if (this.type === NumericRangeType.ArrayRange) {
367
+ const value = this.value as number[];
368
+ if (value[0] < 0 || value[1] < 0) {
369
+ return false;
370
+ }
371
+ }
372
+ if (this.type === NumericRangeType.SingleValue) {
373
+ const value = this.value as number;
374
+ // istanbul ignore next
375
+ if (value < 0) {
376
+ return false;
377
+ }
378
+ }
379
+ return this.type !== NumericRangeType.InvalidRange;
380
+ }
381
+
382
+ public isEmpty(): boolean {
383
+ return this.type === NumericRangeType.Empty;
384
+ }
385
+
386
+ public isDefined(): boolean {
387
+ return this.type !== NumericRangeType.Empty && this.type !== NumericRangeType.InvalidRange;
388
+ }
389
+
390
+ public toString(): string {
391
+ function array_range_to_string(values: number[]): string {
392
+ assert(Array.isArray(values));
393
+ if (values.length === 2 && values[0] === values[1]) {
394
+ return values[0].toString();
395
+ }
396
+ return values.map((value) => value.toString(10)).join(":");
397
+ }
398
+
399
+ function matrix_range_to_string(values: any) {
400
+ return values
401
+ .map((value: any) => {
402
+ return Array.isArray(value) ? array_range_to_string(value) : value.toString(10);
403
+ })
404
+ .join(",");
405
+ }
406
+
407
+ switch (this.type) {
408
+ case NumericRangeType.SingleValue:
409
+ return (this.value as any).toString(10);
410
+
411
+ case NumericRangeType.ArrayRange:
412
+ return array_range_to_string(this.value as number[]);
413
+
414
+ case NumericRangeType.Empty:
415
+ return NUMERIC_RANGE_EMPTY_STRING;
416
+
417
+ case NumericRangeType.MatrixRange:
418
+ return matrix_range_to_string(this.value);
419
+
420
+ default:
421
+ assert(this.type === NumericRangeType.InvalidRange);
422
+ return "NumericRange:<Invalid>";
423
+ }
424
+ }
425
+
426
+ public toJSON(): string {
427
+ return this.toString();
428
+ }
429
+
430
+ public toEncodeableString(): UAString {
431
+ switch (this.type) {
432
+ case NumericRangeType.SingleValue:
433
+ case NumericRangeType.ArrayRange:
434
+ case NumericRangeType.MatrixRange:
435
+ return this.toString();
436
+ case NumericRangeType.InvalidRange:
437
+ // istanbul ignore next
438
+ if (!(typeof this.value === "string")) {
439
+ throw new Error("Internal Error");
440
+ }
441
+ return this.value; // value contains the original strings which was detected invalid
442
+ default:
443
+ return null;
444
+ }
445
+ }
446
+
447
+ /**
448
+ * @method extract_values
449
+ * @param array flat array containing values or string
450
+ * @param dimensions: of the matrix if data is a matrix
451
+ * @return {*}
452
+ */
453
+ public extract_values<U, T extends ArrayLike<U>>(array: T, dimensions?: number[]): ExtractResult<T> {
454
+ const self = this as NumericalRange0;
455
+
456
+ if (!array) {
457
+ return {
458
+ array,
459
+ statusCode: this.type === NumericRangeType.Empty ? StatusCodes.Good : StatusCodes.BadIndexRangeNoData
460
+ };
461
+ }
462
+
463
+ let index;
464
+ let low_index;
465
+ let high_index;
466
+ let rowRange;
467
+ let colRange;
468
+ switch (self.type) {
469
+ case NumericRangeType.Empty:
470
+ return extract_empty(array, dimensions);
471
+
472
+ case NumericRangeType.SingleValue:
473
+ index = self.value;
474
+ return extract_single_value(array, index);
475
+
476
+ case NumericRangeType.ArrayRange:
477
+ low_index = self.value[0];
478
+ high_index = self.value[1];
479
+ return extract_array_range(array, low_index, high_index);
480
+
481
+ case NumericRangeType.MatrixRange:
482
+ rowRange = self.value[0];
483
+ colRange = self.value[1];
484
+ return extract_matrix_range(array, rowRange, colRange, dimensions);
485
+
486
+ default:
487
+ return { statusCode: StatusCodes.BadIndexRangeInvalid };
488
+ }
489
+ }
490
+
491
+ public set_values_matrix(
492
+ sourceToAlter: { matrix: Buffer | []; dimensions: number[] },
493
+ newMatrix: Buffer | []
494
+ ): { matrix: Buffer | []; statusCode: StatusCode } {
495
+ const { matrix, dimensions } = sourceToAlter;
496
+ const self = this as NumericalRange0;
497
+ assert(dimensions, "expecting valid dimensions here");
498
+ if (self.type !== NumericRangeType.MatrixRange) {
499
+ // istanbul ignore next
500
+ return { matrix, statusCode: StatusCodes.BadTypeMismatch };
501
+ }
502
+
503
+ assert(dimensions.length === 2);
504
+ const nbRows = dimensions[0];
505
+ const nbCols = dimensions[1];
506
+ assert(sourceToAlter.matrix.length === nbRows * nbCols);
507
+ const [rowStart, rowEnd] = self.value[0];
508
+ const [colStart, colEnd] = self.value[1];
509
+
510
+ const nbRowInNew = rowEnd - rowStart + 1;
511
+ const nbColInNew = colEnd - colStart + 1;
512
+ if (nbRowInNew * nbColInNew !== newMatrix.length) {
513
+ return { matrix, statusCode: StatusCodes.BadTypeMismatch };
514
+ }
515
+ // check if the sub-matrix is in th range of the initial matrix
516
+ if (rowEnd >= nbRows || colEnd >= nbCols) {
517
+ // debugLog("out of band range => ", { rowEnd, nbRows, colEnd, nbCols });
518
+ return { matrix, statusCode: StatusCodes.BadTypeMismatch };
519
+ }
520
+ for (let row = rowStart; row <= rowEnd; row++) {
521
+ const ri = row - rowStart;
522
+ for (let col = colStart; col <= colEnd; col++) {
523
+ const ci = col - colStart;
524
+ matrix[row * nbCols + col] = newMatrix[ri * nbColInNew + ci];
525
+ }
526
+ }
527
+ return {
528
+ matrix,
529
+ statusCode: StatusCodes.Good
530
+ };
531
+ }
532
+ public set_values(arrayToAlter: Buffer | [], newValues: Buffer | []): { array: Buffer | []; statusCode: StatusCode } {
533
+ assert_array_or_buffer(arrayToAlter);
534
+ assert_array_or_buffer(newValues);
535
+
536
+ const self = this as NumericalRange0;
537
+
538
+ let low_index;
539
+ let high_index;
540
+ switch (self.type) {
541
+ case NumericRangeType.Empty:
542
+ low_index = 0;
543
+ high_index = arrayToAlter.length - 1;
544
+ break;
545
+ case NumericRangeType.SingleValue:
546
+ low_index = self.value;
547
+ high_index = self.value;
548
+ break;
549
+ case NumericRangeType.ArrayRange:
550
+ low_index = self.value[0];
551
+ high_index = self.value[1];
552
+ break;
553
+ case NumericRangeType.MatrixRange:
554
+ // for the time being MatrixRange is not supported
555
+ return { array: arrayToAlter, statusCode: StatusCodes.BadIndexRangeNoData };
556
+ default:
557
+ return { array: [], statusCode: StatusCodes.BadIndexRangeInvalid };
558
+ }
559
+
560
+ if (high_index >= arrayToAlter.length || low_index >= arrayToAlter.length) {
561
+ return { array: [], statusCode: StatusCodes.BadIndexRangeNoData };
562
+ }
563
+ // istanbul ignore next
564
+ if (this.type !== NumericRangeType.Empty && newValues.length !== high_index - low_index + 1) {
565
+ return { array: [], statusCode: StatusCodes.BadIndexRangeInvalid };
566
+ }
567
+ const insertInPlace = Array.isArray(arrayToAlter)
568
+ ? insertInPlaceStandardArray
569
+ : arrayToAlter instanceof Buffer
570
+ ? insertInPlaceBuffer
571
+ : insertInPlaceTypedArray;
572
+ return {
573
+ array: insertInPlace(arrayToAlter, low_index, high_index, newValues),
574
+ statusCode: StatusCodes.Good
575
+ };
576
+ }
577
+ }
578
+
579
+ function slice<U, T extends ArrayLike<U>>(arr: T, start: number, end: number): T {
580
+ if (start === 0 && end === arr.length) {
581
+ return arr;
582
+ }
583
+
584
+ let res;
585
+ if ((arr as any).buffer instanceof ArrayBuffer) {
586
+ res = (arr as any).subarray(start, end);
587
+ } else {
588
+ assert(typeof (arr as any).slice === "function");
589
+ assert(arr instanceof Buffer || arr instanceof Array || typeof arr === "string");
590
+ res = (arr as any).slice(start, end);
591
+ }
592
+ if (res instanceof Uint8Array && arr instanceof Buffer) {
593
+ // note in io-js 3.00 onward standard Buffer are implemented differently and
594
+ // provides a buffer member and a subarray method, in fact in io-js 3.0
595
+ // it seems that Buffer acts as a Uint8Array. in this very special case
596
+ // we need to make sure that we end up with a Buffer object and not a Uint8Array.
597
+ res = Buffer.from(res);
598
+ }
599
+ return res;
600
+ }
601
+
602
+ export interface ExtractResult<T> {
603
+ array?: T;
604
+ statusCode: StatusCode;
605
+ dimensions?: number[];
606
+ }
607
+
608
+ function extract_empty<U, T extends ArrayLike<U>>(array: T, dimensions: any): ExtractResult<T> {
609
+ return {
610
+ array: slice(array, 0, array.length),
611
+ dimensions,
612
+ statusCode: StatusCodes.Good
613
+ };
614
+ }
615
+
616
+ function extract_single_value<U, T extends ArrayLike<U>>(array: T, index: number): ExtractResult<T> {
617
+ if (index >= array.length) {
618
+ if (typeof array === "string") {
619
+ return { array: "" as any as T, statusCode: StatusCodes.BadIndexRangeNoData };
620
+ }
621
+ return { array: [] as any as T, statusCode: StatusCodes.BadIndexRangeNoData };
622
+ }
623
+ return {
624
+ array: slice(array, index, index + 1),
625
+ statusCode: StatusCodes.Good
626
+ };
627
+ }
628
+
629
+ function extract_array_range<U, T extends ArrayLike<U>>(array: T, low_index: number, high_index: number): ExtractResult<T> {
630
+ assert(isFinite(low_index) && isFinite(high_index));
631
+ assert(low_index >= 0);
632
+ assert(low_index <= high_index);
633
+ if (low_index >= array.length) {
634
+ if (typeof array === "string") {
635
+ return { array: "" as any as T, statusCode: StatusCodes.BadIndexRangeNoData };
636
+ }
637
+ return { array: [] as any as T, statusCode: StatusCodes.BadIndexRangeNoData };
638
+ }
639
+ // clamp high index
640
+ high_index = Math.min(high_index, array.length - 1);
641
+
642
+ return {
643
+ array: slice(array, low_index, high_index + 1),
644
+ statusCode: StatusCodes.Good
645
+ };
646
+ }
647
+
648
+ function isArrayLike(value: any): boolean {
649
+ return typeof value.length === "number" || Object.prototype.hasOwnProperty.call(value, "length");
650
+ }
651
+
652
+ function extract_matrix_range<U, T extends ArrayLike<U>>(
653
+ array: T,
654
+ rowRange: number[],
655
+ colRange: number[],
656
+ dimension?: number[]
657
+ ): ExtractResult<T> {
658
+ assert(Array.isArray(rowRange) && Array.isArray(colRange));
659
+
660
+ if (array.length === 0) {
661
+ return {
662
+ array: [] as any as T,
663
+ statusCode: StatusCodes.BadIndexRangeNoData
664
+ };
665
+ }
666
+ if (isArrayLike((array as any)[0]) && !dimension) {
667
+ // like extracting data from a one dimensional array of strings or byteStrings...
668
+ const result = extract_array_range(array, rowRange[0], rowRange[1]);
669
+ for (let i = 0; i < result.array!.length; i++) {
670
+ const e = (result.array! as any)[i];
671
+ (result.array as any)[i] = extract_array_range(e, colRange[0], colRange[1]).array;
672
+ }
673
+ return result;
674
+ }
675
+ if (!dimension) {
676
+ return {
677
+ array: [] as any as T,
678
+ statusCode: StatusCodes.BadIndexRangeNoData
679
+ };
680
+ }
681
+
682
+ assert(dimension, "expecting dimension to know the shape of the matrix represented by the flat array");
683
+
684
+ //
685
+ const rowLow = rowRange[0];
686
+ const rowHigh = rowRange[1];
687
+ const colLow = colRange[0];
688
+ const colHigh = colRange[1];
689
+
690
+ const nbRow = dimension[0];
691
+ const nbCol = dimension[1];
692
+
693
+ const nbRowDest = rowHigh - rowLow + 1;
694
+ const nbColDest = colHigh - colLow + 1;
695
+
696
+ // construct an array of the same type with the appropriate length to
697
+ // store the extracted matrix.
698
+ const tmp = new (array as any).constructor(nbColDest * nbRowDest);
699
+
700
+ let row;
701
+ let col;
702
+ let r;
703
+ let c;
704
+ r = 0;
705
+ for (row = rowLow; row <= rowHigh; row++) {
706
+ c = 0;
707
+ for (col = colLow; col <= colHigh; col++) {
708
+ const srcIndex = row * nbCol + col;
709
+ const destIndex = r * nbColDest + c;
710
+ tmp[destIndex] = (array as any)[srcIndex];
711
+ c++;
712
+ }
713
+ r += 1;
714
+ }
715
+ return {
716
+ array: tmp,
717
+ dimensions: [nbRowDest, nbColDest],
718
+ statusCode: StatusCodes.Good
719
+ };
720
+ }
721
+
722
+ function assert_array_or_buffer(array: any) {
723
+ assert(Array.isArray(array) || array.buffer instanceof ArrayBuffer || array instanceof Buffer);
724
+ }
725
+
726
+ function insertInPlaceStandardArray(arrayToAlter: any, low: number, high: number, newValues: any): any {
727
+ const args = [low, high - low + 1].concat(newValues);
728
+ arrayToAlter.splice(...args);
729
+ return arrayToAlter;
730
+ }
731
+
732
+ function insertInPlaceTypedArray(arrayToAlter: any, low: number, high: number, newValues: any): any {
733
+ if (low === 0 && high === arrayToAlter.length - 1) {
734
+ return new arrayToAlter.constructor(newValues);
735
+ }
736
+ assert(newValues.length === high - low + 1);
737
+ arrayToAlter.subarray(low, high + 1).set(newValues);
738
+ return arrayToAlter;
739
+ }
740
+
741
+ function insertInPlaceBuffer(bufferToAlter: any, low: number, high: number, newValues: any): any {
742
+ if (low === 0 && high === bufferToAlter.length - 1) {
743
+ return Buffer.from(newValues);
744
+ }
745
+ assert(newValues.length === high - low + 1);
746
+ for (let i = 0; i < newValues.length; i++) {
747
+ bufferToAlter[i + low] = newValues[i];
748
+ }
749
+ return bufferToAlter;
750
+ }
751
+
752
+ function _overlap(l1: number, h1: number, l2: number, h2: number): boolean {
753
+ return Math.max(l1, l2) <= Math.min(h1, h2);
754
+ }
755
+
756
+ export function encodeNumericRange(numericRange: NumericRange, stream: OutputBinaryStream): void {
757
+ assert(numericRange instanceof NumericRange);
758
+ encodeString(numericRange.toEncodeableString(), stream);
759
+ }
760
+
761
+ export function decodeNumericRange(stream: BinaryStream, _value?: NumericRange): NumericRange {
762
+ const str = decodeString(stream)!;
763
+ return new NumericRange(str);
764
+ }
765
+
766
+ function coerceNumericRange(value: any | string | NumericRange | null | number[]): NumericRange {
767
+ if (value instanceof NumericRange) {
768
+ return value;
769
+ }
770
+ if (value === null || value === undefined) {
771
+ return new NumericRange();
772
+ }
773
+ if (value === NUMERIC_RANGE_EMPTY_STRING) {
774
+ return new NumericRange();
775
+ }
776
+ assert(typeof value === "string" || Array.isArray(value));
777
+ return new NumericRange(value);
778
+ }