@abaplint/runtime 2.13.93 → 2.13.94

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.
@@ -0,0 +1,9 @@
1
+ import { DecFloat34, Float, Integer, Integer8, Packed } from "../types";
2
+ import { INumeric } from "../types/_numeric";
3
+ export type ExtremumArgument = number | INumeric | undefined;
4
+ /** nmax( ) and nmin( ): the result has the calculation type of the arguments,
5
+ * as an arithmetic expression would determine it - decfloat34 before f before
6
+ * p before int8 before i. So nmin( val1 = i1 val2 = i2 ) is an i, which DO
7
+ * nmin( ... ) TIMES and every other integer position rely on, and a p result
8
+ * keeps the most decimals of its arguments. */
9
+ export declare function extremum(args: ExtremumArgument[], isBetter: (candidate: number, best: number) => boolean): Float | Integer8 | Integer | DecFloat34 | Packed;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extremum = extremum;
4
+ const _parse_1 = require("../operators/_parse");
5
+ const types_1 = require("../types");
6
+ // the calculation types in the order an arithmetic expression ranks them
7
+ const INT = 0;
8
+ const INT8 = 1;
9
+ const PACKED = 2;
10
+ const FLOAT = 3;
11
+ const DECFLOAT = 4;
12
+ /** nmax( ) and nmin( ): the result has the calculation type of the arguments,
13
+ * as an arithmetic expression would determine it - decfloat34 before f before
14
+ * p before int8 before i. So nmin( val1 = i1 val2 = i2 ) is an i, which DO
15
+ * nmin( ... ) TIMES and every other integer position rely on, and a p result
16
+ * keeps the most decimals of its arguments. */
17
+ function extremum(args, isBetter) {
18
+ let best = undefined;
19
+ let bestValue = 0;
20
+ let type = INT;
21
+ let decimals = 0;
22
+ for (let arg of args) {
23
+ if (arg === undefined) {
24
+ continue;
25
+ }
26
+ if (arg instanceof types_1.FieldSymbol) {
27
+ arg = arg.getPointer();
28
+ }
29
+ if (arg instanceof types_1.DecFloat34) {
30
+ type = Math.max(type, DECFLOAT);
31
+ }
32
+ else if (arg instanceof types_1.Float) {
33
+ type = Math.max(type, FLOAT);
34
+ }
35
+ else if (arg instanceof types_1.Packed) {
36
+ type = Math.max(type, PACKED);
37
+ decimals = Math.max(decimals, arg.getDecimals());
38
+ }
39
+ else if (arg instanceof types_1.Integer8) {
40
+ type = Math.max(type, INT8);
41
+ }
42
+ else if (arg instanceof types_1.Integer || (typeof arg === "number" && Number.isInteger(arg))) {
43
+ // i, the lowest type
44
+ }
45
+ else {
46
+ // character-like and other arguments, as before: f
47
+ type = Math.max(type, FLOAT);
48
+ }
49
+ const value = (0, _parse_1.parse)(arg);
50
+ if (best === undefined || isBetter(value, bestValue)) {
51
+ best = arg;
52
+ bestValue = value;
53
+ }
54
+ }
55
+ switch (type) {
56
+ case DECFLOAT:
57
+ return new types_1.DecFloat34().set(bestValue);
58
+ case FLOAT:
59
+ return new types_1.Float().set(bestValue);
60
+ case PACKED:
61
+ if (best instanceof types_1.Packed) {
62
+ // through the digits, not through a binary float
63
+ return new types_1.Packed({ length: 16, decimals }).set(best.toFixed(best.getDecimals()));
64
+ }
65
+ return new types_1.Packed({ length: 16, decimals }).set(bestValue);
66
+ case INT8:
67
+ if (best instanceof types_1.Integer8) {
68
+ return new types_1.Integer8().set(best.get());
69
+ }
70
+ return new types_1.Integer8().set(bestValue);
71
+ default:
72
+ return new types_1.Integer().set(bestValue);
73
+ }
74
+ }
75
+ //# sourceMappingURL=_extremum.js.map
@@ -1,5 +1,4 @@
1
1
  import { INumeric } from "../types/_numeric";
2
- import { Float } from "../types";
3
2
  export interface INmaxInput {
4
3
  val1: number | INumeric;
5
4
  val2: number | INumeric;
@@ -11,4 +10,4 @@ export interface INmaxInput {
11
10
  val8?: number | INumeric;
12
11
  val9?: number | INumeric;
13
12
  }
14
- export declare function nmax(input: INmaxInput): Float;
13
+ export declare function nmax(input: INmaxInput): import("../types").Float | import("../types").Integer8 | import("../types").Integer | import("../types").DecFloat34 | import("../types").Packed;
@@ -1,34 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nmax = nmax;
4
- const _parse_1 = require("../operators/_parse");
5
- const types_1 = require("../types");
4
+ const _extremum_1 = require("./_extremum");
6
5
  function nmax(input) {
7
- const values = [];
8
- values.push((0, _parse_1.parse)(input.val1));
9
- values.push((0, _parse_1.parse)(input.val2));
10
- if (input.val3) {
11
- values.push((0, _parse_1.parse)(input.val3));
12
- }
13
- if (input.val4) {
14
- values.push((0, _parse_1.parse)(input.val4));
15
- }
16
- if (input.val5) {
17
- values.push((0, _parse_1.parse)(input.val5));
18
- }
19
- if (input.val6) {
20
- values.push((0, _parse_1.parse)(input.val6));
21
- }
22
- if (input.val7) {
23
- values.push((0, _parse_1.parse)(input.val7));
24
- }
25
- if (input.val8) {
26
- values.push((0, _parse_1.parse)(input.val8));
27
- }
28
- if (input.val9) {
29
- values.push((0, _parse_1.parse)(input.val9));
30
- }
31
- values.sort((a, b) => (b - a));
32
- return new types_1.Float().set(values[0]);
6
+ return (0, _extremum_1.extremum)([input.val1, input.val2, input.val3, input.val4, input.val5,
7
+ input.val6, input.val7, input.val8, input.val9], (candidate, best) => candidate > best);
33
8
  }
34
9
  //# sourceMappingURL=nmax.js.map
@@ -1,5 +1,4 @@
1
1
  import { INumeric } from "../types/_numeric";
2
- import { Float } from "../types";
3
2
  export interface INminInput {
4
3
  val1: number | INumeric;
5
4
  val2: number | INumeric;
@@ -11,4 +10,4 @@ export interface INminInput {
11
10
  val8?: number | INumeric;
12
11
  val9?: number | INumeric;
13
12
  }
14
- export declare function nmin(input: INminInput): Float;
13
+ export declare function nmin(input: INminInput): import("../types").Float | import("../types").Integer8 | import("../types").Integer | import("../types").DecFloat34 | import("../types").Packed;
@@ -1,34 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nmin = nmin;
4
- const _parse_1 = require("../operators/_parse");
5
- const types_1 = require("../types");
4
+ const _extremum_1 = require("./_extremum");
6
5
  function nmin(input) {
7
- const values = [];
8
- values.push((0, _parse_1.parse)(input.val1));
9
- values.push((0, _parse_1.parse)(input.val2));
10
- if (input.val3) {
11
- values.push((0, _parse_1.parse)(input.val3));
12
- }
13
- if (input.val4) {
14
- values.push((0, _parse_1.parse)(input.val4));
15
- }
16
- if (input.val5) {
17
- values.push((0, _parse_1.parse)(input.val5));
18
- }
19
- if (input.val6) {
20
- values.push((0, _parse_1.parse)(input.val6));
21
- }
22
- if (input.val7) {
23
- values.push((0, _parse_1.parse)(input.val7));
24
- }
25
- if (input.val8) {
26
- values.push((0, _parse_1.parse)(input.val8));
27
- }
28
- if (input.val9) {
29
- values.push((0, _parse_1.parse)(input.val9));
30
- }
31
- values.sort((a, b) => (a - b));
32
- return new types_1.Float().set(values[0]);
6
+ return (0, _extremum_1.extremum)([input.val1, input.val2, input.val3, input.val4, input.val5,
7
+ input.val6, input.val7, input.val8, input.val9], (candidate, best) => candidate < best);
33
8
  }
34
9
  //# sourceMappingURL=nmin.js.map
@@ -1,8 +1,17 @@
1
- import { Float } from "../types";
1
+ import { DecFloat34 } from "../types";
2
2
  import { ICharacter } from "../types/_character";
3
3
  import { INumeric } from "../types/_numeric";
4
+ type Input = INumeric | ICharacter | string | number;
5
+ /** round( val = arg {dec = n}|{prec = n} [mode = m] )
6
+ *
7
+ * The argument is converted to decfloat34 and rounded as a decimal number, so
8
+ * round( val = '2.675' dec = 2 ) is 2.68 - not 2.67, which is what scaling the
9
+ * nearest binary float would give. A binary float argument brings its full
10
+ * binary expansion along, the way converting f to decfloat34 does. */
4
11
  export declare function round(input: {
5
- val: ICharacter | string;
6
- dec: INumeric | number;
7
- mode?: INumeric | number;
8
- }): Float;
12
+ val: Input;
13
+ dec?: Input;
14
+ prec?: Input;
15
+ mode?: Input;
16
+ }): DecFloat34;
17
+ export {};
@@ -3,39 +3,87 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.round = round;
4
4
  const _parse_1 = require("../operators/_parse");
5
5
  const types_1 = require("../types");
6
+ // the rounding modes of CL_ABAP_MATH, the decNumber enumeration
7
+ const ROUND_CEILING = 0;
8
+ const ROUND_UP = 1;
9
+ const ROUND_HALF_UP = 2;
10
+ const ROUND_HALF_EVEN = 3;
11
+ const ROUND_HALF_DOWN = 4;
12
+ const ROUND_DOWN = 5;
13
+ const ROUND_FLOOR = 6;
14
+ /** round( val = arg {dec = n}|{prec = n} [mode = m] )
15
+ *
16
+ * The argument is converted to decfloat34 and rounded as a decimal number, so
17
+ * round( val = '2.675' dec = 2 ) is 2.68 - not 2.67, which is what scaling the
18
+ * nearest binary float would give. A binary float argument brings its full
19
+ * binary expansion along, the way converting f to decfloat34 does. */
6
20
  function round(input) {
7
- let mode = input.mode;
8
- if (mode === undefined) {
9
- mode = 2;
21
+ const mode = input.mode === undefined ? ROUND_HALF_UP : (0, _parse_1.parse)(input.mode);
22
+ let arg = input.val;
23
+ if (arg instanceof types_1.FieldSymbol) {
24
+ arg = arg.getPointer();
10
25
  }
11
- else if (typeof mode !== "number") {
12
- mode = mode?.get();
26
+ const val = (0, _parse_1.parse)(arg);
27
+ const text = arg instanceof types_1.Float ? val.toPrecision(34) : String(val);
28
+ const match = /^(-?)(\d*)\.?(\d*)(?:e([+-]?\d+))?$/i.exec(text);
29
+ if (match === null || !Number.isFinite(val)) {
30
+ return new types_1.DecFloat34().set(val);
13
31
  }
14
- const val = (0, _parse_1.parse)(input.val);
15
- const dec = (0, _parse_1.parse)(input.dec);
16
- if (dec !== 0) {
17
- throw new Error("round(), todo, handle decimals");
32
+ const negative = match[1] === "-";
33
+ const digits = BigInt((match[2] + match[3]).replace(/^0+/, "") || "0");
34
+ // the value is digits * 10^exponent
35
+ const exponent = parseInt(match[4] || "0", 10) - match[3].length;
36
+ let dec;
37
+ if (input.prec !== undefined) {
38
+ const prec = (0, _parse_1.parse)(input.prec);
39
+ if (prec <= 0) {
40
+ throw new Error("CX_SY_ARITHMETIC_ERROR, round( ) prec must be positive");
41
+ }
42
+ // position of the most significant digit
43
+ const top = digits.toString().length - 1 + exponent;
44
+ dec = prec - 1 - top;
18
45
  }
19
- const ret = new types_1.Float();
46
+ else {
47
+ dec = input.dec === undefined ? 0 : (0, _parse_1.parse)(input.dec);
48
+ }
49
+ const drop = -exponent - dec;
50
+ if (drop <= 0 || digits === 0n) {
51
+ return new types_1.DecFloat34().set(val);
52
+ }
53
+ const divisor = 10n ** BigInt(drop);
54
+ let kept = digits / divisor;
55
+ const rest = digits % divisor;
56
+ // compare the dropped part with one half of the last kept digit
57
+ const half = rest * 2n === divisor ? 0 : (rest * 2n > divisor ? 1 : -1);
58
+ let away;
20
59
  switch (mode) {
21
- case 1:
22
- ret.set(Math.ceil(val));
60
+ case ROUND_CEILING:
61
+ away = rest > 0n && negative === false;
62
+ break;
63
+ case ROUND_UP:
64
+ away = rest > 0n;
23
65
  break;
24
- case 2:
25
- ret.set(Math.round(val));
66
+ case ROUND_HALF_UP:
67
+ away = half >= 0;
26
68
  break;
27
- case 4:
28
- ret.set(-Math.round(-val));
69
+ case ROUND_HALF_EVEN:
70
+ away = half > 0 || (half === 0 && kept % 2n === 1n);
29
71
  break;
30
- case 5:
31
- ret.set(Math.trunc(val));
72
+ case ROUND_HALF_DOWN:
73
+ away = half > 0;
32
74
  break;
33
- case 6:
34
- ret.set(Math.floor(val));
75
+ case ROUND_DOWN:
76
+ away = false;
77
+ break;
78
+ case ROUND_FLOOR:
79
+ away = rest > 0n && negative === true;
35
80
  break;
36
81
  default:
37
82
  throw new Error("round(), unknown mode: " + mode);
38
83
  }
39
- return ret;
84
+ if (away) {
85
+ kept += 1n;
86
+ }
87
+ return new types_1.DecFloat34().set(Number((negative ? "-" : "") + kept.toString() + "e" + (-dec)));
40
88
  }
41
89
  //# sourceMappingURL=round.js.map
@@ -347,6 +347,16 @@ function eq(left, right) {
347
347
  else if (right instanceof types_1.Float && typeof l === "number") {
348
348
  r = right.getRaw();
349
349
  }
350
+ else if ((right instanceof types_1.Float || right instanceof types_1.DecFloat34) && typeof l === "string") {
351
+ // a character-like operand is compared as a number: get( ) of a float
352
+ // writes 1,4800000000000000E+02, which no text compares equal with
353
+ r = right.getRaw();
354
+ l = Number(l);
355
+ }
356
+ else if (left instanceof types_1.DecFloat34 && typeof r === "string") {
357
+ l = left.getRaw();
358
+ r = Number(r);
359
+ }
350
360
  else if (left instanceof types_1.Float) {
351
361
  if (typeof r === "number") {
352
362
  l = left.getRaw();
@@ -1,4 +1,6 @@
1
1
  import { Table } from "../types";
2
2
  import { ICharacter } from "../types/_character";
3
3
  import { INumeric } from "../types/_numeric";
4
- export declare function compareIn(left: number | string | ICharacter | INumeric, right: Table): boolean;
4
+ type Operand = number | string | ICharacter | INumeric;
5
+ export declare function compareIn(left: Operand, right: Table): boolean;
6
+ export {};
@@ -3,32 +3,49 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compareIn = compareIn;
4
4
  const cp_1 = require("./cp");
5
5
  const eq_1 = require("./eq");
6
+ const ge_1 = require("./ge");
7
+ const gt_1 = require("./gt");
8
+ const le_1 = require("./le");
9
+ const lt_1 = require("./lt");
6
10
  const ne_1 = require("./ne");
7
- function compareIn(left, right) {
8
- if (right.array().length === 0) {
9
- return true;
11
+ function matches(left, option, low, high) {
12
+ switch (option) {
13
+ case "EQ": return (0, eq_1.eq)(left, low);
14
+ case "NE": return (0, ne_1.ne)(left, low);
15
+ case "GT": return (0, gt_1.gt)(left, low);
16
+ case "GE": return (0, ge_1.ge)(left, low);
17
+ case "LT": return (0, lt_1.lt)(left, low);
18
+ case "LE": return (0, le_1.le)(left, low);
19
+ case "BT": return (0, ge_1.ge)(left, low) && (0, le_1.le)(left, high);
20
+ case "NB": return !((0, ge_1.ge)(left, low) && (0, le_1.le)(left, high));
21
+ case "CP": return (0, cp_1.cp)(left, low);
22
+ case "NP": return !(0, cp_1.cp)(left, low);
23
+ default: throw new Error("compareIn, unknown option " + option);
10
24
  }
25
+ }
26
+ // A value is in a range table when some I row admits it, or there is no I
27
+ // row at all, and no E row admits it. An empty table admits everything.
28
+ function compareIn(left, right) {
29
+ let included = false;
30
+ let hasInclude = false;
11
31
  for (const row of right.array()) {
12
- if ((0, eq_1.eq)(row.get()["sign"], "I") && (0, eq_1.eq)(row.get()["option"], "EQ")) {
13
- if ((0, eq_1.eq)(row.get()["low"], left)) {
14
- return true;
15
- }
16
- }
17
- else if ((0, eq_1.eq)(row.get()["sign"], "E") && (0, eq_1.eq)(row.get()["option"], "EQ")) {
18
- if ((0, ne_1.ne)(row.get()["low"], left)) {
19
- return true;
20
- }
32
+ const r = row.get();
33
+ const sign = r["sign"].get().toString().toUpperCase().trim();
34
+ const option = r["option"].get().toString().toUpperCase().trim();
35
+ const hit = matches(left, option, r["low"], r["high"]);
36
+ if (sign === "I") {
37
+ hasInclude = true;
38
+ included = included || hit;
21
39
  }
22
- else if ((0, eq_1.eq)(row.get()["sign"], "I") && (0, eq_1.eq)(row.get()["option"], "CP")) {
23
- if ((0, cp_1.cp)(left, row.get()["low"])) {
24
- return true;
40
+ else if (sign === "E") {
41
+ if (hit) {
42
+ return false;
25
43
  }
26
44
  }
27
45
  else {
28
- console.dir(row);
29
- throw new Error("compareIn todo");
46
+ throw new Error("compareIn, unknown sign " + sign);
30
47
  }
31
48
  }
32
- return false;
49
+ return hasInclude === false || included;
33
50
  }
34
51
  //# sourceMappingURL=in.js.map
@@ -21,6 +21,18 @@ function createDataRefTo(target, name) {
21
21
  }
22
22
  target.assign(new abap.types.ABAPObject({ qualifiedName: name, RTTIName: name }));
23
23
  }
24
+ /** CREATE DATA ... LIKE dobj takes the type of dobj, never its content: the
25
+ * created data object starts initial. A table is created empty right away
26
+ * instead of copying its rows first. */
27
+ function initialLike(like) {
28
+ if (like instanceof types_1.Table || like instanceof types_1.HashedTable) {
29
+ const TableType = like.constructor;
30
+ return new TableType(like.getRowType(), like.getOptions(), like.getQualifiedName());
31
+ }
32
+ const copy = like.clone();
33
+ copy.clear();
34
+ return copy;
35
+ }
24
36
  function createData(target, options) {
25
37
  // console.dir(options);
26
38
  if (target instanceof types_1.FieldSymbol) {
@@ -208,10 +220,10 @@ function createData(target, options) {
208
220
  options.like = options.like.getPointer();
209
221
  }
210
222
  if (options.table === true) {
211
- target.assign(new abap.types.Table(options.like.clone(), tableOptions));
223
+ target.assign(new abap.types.Table(initialLike(options.like), tableOptions));
212
224
  }
213
225
  else {
214
- target.assign(options.like.clone());
226
+ target.assign(initialLike(options.like));
215
227
  }
216
228
  }
217
229
  else {
@@ -90,6 +90,73 @@ async function searchWithKeyPromise(arr, withKey, startIndex = 0, usesTableLine)
90
90
  };
91
91
  }
92
92
  /////////////////
93
+ function toRow(a, isStructured, usesTableLine) {
94
+ if (usesTableLine === false && isStructured === true) {
95
+ return a.get();
96
+ }
97
+ return isStructured ? { table_line: a, ...a.get() } : { table_line: a };
98
+ }
99
+ /** READ with a key on a SORTED table: binary search over the leading key components that are given,
100
+ * a miss gives sy-subrc 4 and sy-tabix of the row the key would be inserted before,
101
+ * or sy-subrc 8 and sy-tabix lines + 1 when it would be inserted after the last row */
102
+ /** true when every component of the condition is a component of the key */
103
+ function onlyKeyComponents(keyFields, options) {
104
+ const key = keyFields.map(k => k.toLowerCase());
105
+ return Object.keys(options.withKeySimple || {}).every(n => key.includes(n.toLowerCase()));
106
+ }
107
+ function readSortedWithKey(arr, keyFields, options) {
108
+ const names = Object.keys(options.withKeySimple).map(n => n.toLowerCase());
109
+ const prefix = [];
110
+ for (const keyField of keyFields) {
111
+ const index = names.indexOf(keyField.toLowerCase());
112
+ if (index < 0) {
113
+ break;
114
+ }
115
+ prefix.push(options.withKeyValue[index]);
116
+ }
117
+ if (prefix.length === 0) {
118
+ const searchResult = searchWithKey(arr, options.withKey, 0, options.usesTableLine);
119
+ return { found: searchResult.found, foundIndex: searchResult.foundIndex, subrc: undefined };
120
+ }
121
+ const isStructured = arr[0] instanceof types_1.Structure;
122
+ const comparePrefix = (row) => {
123
+ for (const p of prefix) {
124
+ const value = p.key(row);
125
+ // eq first, lt and gt compare a c operand with its trailing blanks against a string
126
+ if ((0, compare_1.eq)(value, p.value)) {
127
+ continue;
128
+ }
129
+ else if ((0, compare_1.lt)(value, p.value)) {
130
+ return -1;
131
+ }
132
+ else if ((0, compare_1.gt)(value, p.value)) {
133
+ return 1;
134
+ }
135
+ }
136
+ return 0;
137
+ };
138
+ let left = 0;
139
+ let right = arr.length;
140
+ while (left < right) {
141
+ const middle = Math.floor((left + right) / 2);
142
+ if (comparePrefix(toRow(arr[middle], isStructured, options.usesTableLine)) < 0) {
143
+ left = middle + 1;
144
+ }
145
+ else {
146
+ right = middle;
147
+ }
148
+ }
149
+ for (let index = left; index < arr.length; index++) {
150
+ const row = toRow(arr[index], isStructured, options.usesTableLine);
151
+ if (comparePrefix(row) !== 0) {
152
+ break;
153
+ }
154
+ if (options.withKey(row) === true) {
155
+ return { found: arr[index], foundIndex: index + 1, subrc: undefined };
156
+ }
157
+ }
158
+ return { found: undefined, foundIndex: left + 1, subrc: left >= arr.length ? 8 : 4 };
159
+ }
93
160
  function readTable(table, options) {
94
161
  let found = undefined;
95
162
  let foundIndex = 0;
@@ -187,15 +254,34 @@ function readTable(table, options) {
187
254
  // fallback
188
255
  return readTable(table, { ...options, withKeySimple: undefined });
189
256
  }
190
- const startIndex = (0, binary_search_1.binarySearchFrom)(arr, 0, arr.length, firstKeyName.toLowerCase(), firstValue) - 1;
191
- // console.dir("startindex: " + startIndex);
192
- if (startIndex >= 0) {
193
- const searchResult = searchWithKeyEarlyExit(arr, options.withKey, startIndex, options.usesTableLine, firstKeyName, firstValue);
194
- // console.dir(searchResult);
195
- found = searchResult.found;
196
- foundIndex = searchResult.foundIndex;
257
+ if (keyInformation?.type === types_1.TableAccessType.sorted
258
+ && options.withKeyValue
259
+ && onlyKeyComponents(keyInformation.keyFields, options)) {
260
+ const result = readSortedWithKey(arr, keyInformation.keyFields, options);
261
+ found = result.found;
262
+ foundIndex = result.foundIndex;
263
+ binarySubrc = result.subrc;
264
+ }
265
+ else {
266
+ const startIndex = (0, binary_search_1.binarySearchFrom)(arr, 0, arr.length, firstKeyName.toLowerCase(), firstValue) - 1;
267
+ if (startIndex >= 0) {
268
+ const searchResult = searchWithKeyEarlyExit(arr, options.withKey, startIndex, options.usesTableLine, firstKeyName, firstValue);
269
+ found = searchResult.found;
270
+ foundIndex = searchResult.foundIndex;
271
+ }
197
272
  }
198
273
  }
274
+ else if (options?.withTableKey === true
275
+ && options.withKeyValue
276
+ && options.withKeySimple
277
+ && options.withKey
278
+ && table.getOptions().primaryKey?.type === types_1.TableAccessType.sorted
279
+ && onlyKeyComponents(table.getOptions().primaryKey?.keyFields || [], options)) {
280
+ const result = readSortedWithKey(table.array(), table.getOptions().primaryKey?.keyFields || [], options);
281
+ found = result.found;
282
+ foundIndex = result.foundIndex;
283
+ binarySubrc = result.subrc;
284
+ }
199
285
  else if ((options?.binarySearch === true || options?.withTableKey === true)
200
286
  && options.withKeyValue
201
287
  && (options?.binarySearch === true || table.getOptions().primaryKey?.type !== types_1.TableAccessType.standard)
@@ -3,6 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.templateFormatting = templateFormatting;
4
4
  const alpha_1 = require("./alpha");
5
5
  const types_1 = require("./types");
6
+ /** DECIMALS for a decimal number: its decimal digits are rounded, half away
7
+ * from zero, the way ABAP rounds commercially - 2.675 gives 2.68, where
8
+ * toFixed( ) on the nearest binary float gives 2.67 */
9
+ function decimalsOfDecimal(value, decimals) {
10
+ const shift = (x, places) => {
11
+ const [mantissa, exponent] = (x + "").split("e");
12
+ return Number(mantissa + "e" + (Number(exponent || 0) + places));
13
+ };
14
+ const rounded = shift(Math.round(shift(Math.abs(value), decimals)), -decimals);
15
+ return (value < 0 && rounded !== 0 ? "-" : "") + rounded.toFixed(decimals);
16
+ }
6
17
  function templateFormatting(source, options) {
7
18
  let text = "";
8
19
  if (source instanceof types_1.FieldSymbol) {
@@ -22,7 +33,10 @@ function templateFormatting(source, options) {
22
33
  }
23
34
  else if (source instanceof types_1.DecFloat34) {
24
35
  const raw = source.getRaw();
25
- if (Number.isInteger(raw)) {
36
+ if (options?.decimals !== undefined) {
37
+ text = decimalsOfDecimal(raw, options.decimals);
38
+ }
39
+ else if (Number.isInteger(raw)) {
26
40
  text = raw.toFixed(0);
27
41
  }
28
42
  else {
@@ -43,7 +57,7 @@ function templateFormatting(source, options) {
43
57
  }
44
58
  }
45
59
  else if (source instanceof types_1.Packed) {
46
- if (options?.decimals) {
60
+ if (options?.decimals !== undefined) {
47
61
  text = source.toFixed(options.decimals);
48
62
  }
49
63
  else {
@@ -127,10 +141,10 @@ function templateFormatting(source, options) {
127
141
  else if (options.width) {
128
142
  text = text.trimEnd().padEnd(options.width, " ");
129
143
  }
130
- else if (options.decimals && source instanceof types_1.Integer) {
144
+ else if (options.decimals !== undefined && source instanceof types_1.Integer) {
131
145
  text = source.get().toFixed(options.decimals);
132
146
  }
133
- else if (options.decimals && source instanceof types_1.Float) {
147
+ else if (options.decimals !== undefined && source instanceof types_1.Float) {
134
148
  text = source.getRaw().toFixed(options.decimals);
135
149
  }
136
150
  }
@@ -550,8 +550,18 @@ class Table {
550
550
  return val;
551
551
  }
552
552
  }
553
- /* appends and returns this */
553
+ /* appends and returns this - how VALUE #( ) builds a table, row by row.
554
+ * A sorted table gets each row at its sorted place, as INSERT ... INTO
555
+ * TABLE puts it: pushed, it was in insertion order until something
556
+ * assigned it, so a VALUE #( ) handed straight to a method or looped over
557
+ * was a SORTED table unsorted, and every binary search on it could miss.
558
+ * A duplicate of a unique key is kept, as before */
554
559
  appendThis(item) {
560
+ const primary = this.getOptions()?.primaryKey;
561
+ if (primary?.type === TableAccessType.sorted && !(item instanceof data_reference_1.DataReference)) {
562
+ this.insertSorted(item, (a, b) => (0, sort_1.compareRows)(a, b, primary.keyFields), false);
563
+ return this;
564
+ }
555
565
  this.append(item);
556
566
  return this;
557
567
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/runtime",
3
- "version": "2.13.93",
3
+ "version": "2.13.94",
4
4
  "description": "Transpiler - Runtime",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/src/index.d.ts",