survey-expression 0.0.0-stage → 4.4.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.
- package/LICENSE +201 -0
- package/README.md +104 -2
- package/dist/index.d.ts +256 -0
- package/dist/index.js +1176 -0
- package/package.json +47 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,1176 @@
|
|
|
1
|
+
import { MILLISECONDS_PER_SECOND, exhaustiveGuard, getOrCreate, padWithZeros, takeOne } from "@esposter/shared";
|
|
2
|
+
import { err, ok } from "neverthrow";
|
|
3
|
+
// src/models/SurveyExpressionBinaryOperator.ts
|
|
4
|
+
let SurveyExpressionBinaryOperator = /* @__PURE__ */ function(SurveyExpressionBinaryOperator) {
|
|
5
|
+
SurveyExpressionBinaryOperator["Add"] = "add";
|
|
6
|
+
SurveyExpressionBinaryOperator["AllOf"] = "allOf";
|
|
7
|
+
SurveyExpressionBinaryOperator["And"] = "and";
|
|
8
|
+
SurveyExpressionBinaryOperator["AnyOf"] = "anyOf";
|
|
9
|
+
SurveyExpressionBinaryOperator["Contains"] = "contains";
|
|
10
|
+
SurveyExpressionBinaryOperator["Divide"] = "divide";
|
|
11
|
+
SurveyExpressionBinaryOperator["Equal"] = "equal";
|
|
12
|
+
SurveyExpressionBinaryOperator["Greater"] = "greater";
|
|
13
|
+
SurveyExpressionBinaryOperator["GreaterOrEqual"] = "greaterOrEqual";
|
|
14
|
+
SurveyExpressionBinaryOperator["Less"] = "less";
|
|
15
|
+
SurveyExpressionBinaryOperator["LessOrEqual"] = "lessOrEqual";
|
|
16
|
+
SurveyExpressionBinaryOperator["Modulo"] = "modulo";
|
|
17
|
+
SurveyExpressionBinaryOperator["Multiply"] = "multiply";
|
|
18
|
+
SurveyExpressionBinaryOperator["NotContains"] = "notContains";
|
|
19
|
+
SurveyExpressionBinaryOperator["NotEqual"] = "notEqual";
|
|
20
|
+
SurveyExpressionBinaryOperator["Or"] = "or";
|
|
21
|
+
SurveyExpressionBinaryOperator["Power"] = "power";
|
|
22
|
+
SurveyExpressionBinaryOperator["Subtract"] = "subtract";
|
|
23
|
+
return SurveyExpressionBinaryOperator;
|
|
24
|
+
}({});
|
|
25
|
+
// src/models/SurveyExpressionNodeType.ts
|
|
26
|
+
let SurveyExpressionNodeType = /* @__PURE__ */ function(SurveyExpressionNodeType) {
|
|
27
|
+
SurveyExpressionNodeType["Binary"] = "binary";
|
|
28
|
+
SurveyExpressionNodeType["Call"] = "call";
|
|
29
|
+
SurveyExpressionNodeType["List"] = "list";
|
|
30
|
+
SurveyExpressionNodeType["Literal"] = "literal";
|
|
31
|
+
SurveyExpressionNodeType["Reference"] = "reference";
|
|
32
|
+
SurveyExpressionNodeType["Unary"] = "unary";
|
|
33
|
+
return SurveyExpressionNodeType;
|
|
34
|
+
}({});
|
|
35
|
+
// src/models/SurveyExpressionTokenType.ts
|
|
36
|
+
let SurveyExpressionTokenType = /* @__PURE__ */ function(SurveyExpressionTokenType) {
|
|
37
|
+
SurveyExpressionTokenType["Boolean"] = "boolean";
|
|
38
|
+
SurveyExpressionTokenType["End"] = "end";
|
|
39
|
+
SurveyExpressionTokenType["Identifier"] = "identifier";
|
|
40
|
+
SurveyExpressionTokenType["Number"] = "number";
|
|
41
|
+
SurveyExpressionTokenType["Operator"] = "operator";
|
|
42
|
+
SurveyExpressionTokenType["Punctuation"] = "punctuation";
|
|
43
|
+
SurveyExpressionTokenType["Reference"] = "reference";
|
|
44
|
+
SurveyExpressionTokenType["String"] = "string";
|
|
45
|
+
return SurveyExpressionTokenType;
|
|
46
|
+
}({});
|
|
47
|
+
// src/models/SurveyExpressionUnaryOperator.ts
|
|
48
|
+
let SurveyExpressionUnaryOperator = /* @__PURE__ */ function(SurveyExpressionUnaryOperator) {
|
|
49
|
+
SurveyExpressionUnaryOperator["Empty"] = "empty";
|
|
50
|
+
SurveyExpressionUnaryOperator["Negate"] = "negate";
|
|
51
|
+
SurveyExpressionUnaryOperator["Not"] = "not";
|
|
52
|
+
SurveyExpressionUnaryOperator["NotEmpty"] = "notEmpty";
|
|
53
|
+
return SurveyExpressionUnaryOperator;
|
|
54
|
+
}({});
|
|
55
|
+
// src/services/shared/checkIsSurveyFieldName.ts
|
|
56
|
+
const checkIsSurveyFieldName = (text) => !/[{}><=!]/u.test(text);
|
|
57
|
+
// src/services/shared/getOwnValue.ts
|
|
58
|
+
const getOwnValue = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
|
|
59
|
+
// src/services/shared/getSurveyArrayItems.ts
|
|
60
|
+
const getSurveyArrayItems = (data) => {
|
|
61
|
+
if (Array.isArray(data)) return data;
|
|
62
|
+
if (typeof data === "object") return Object.values(data);
|
|
63
|
+
return [];
|
|
64
|
+
};
|
|
65
|
+
// src/services/shared/toSurveyLogicNumber.ts
|
|
66
|
+
const NUMBER_TEXT_REGEX = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/iu;
|
|
67
|
+
const toSurveyLogicNumber = (value) => {
|
|
68
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
69
|
+
if (typeof value !== "string") return void 0;
|
|
70
|
+
const text = value.trim();
|
|
71
|
+
if (!NUMBER_TEXT_REGEX.test(text)) return void 0;
|
|
72
|
+
const number = Number(text);
|
|
73
|
+
return Number.isFinite(number) ? number : void 0;
|
|
74
|
+
};
|
|
75
|
+
// src/services/shared/getSurveyArrayParams.ts
|
|
76
|
+
const getSurveyArrayParams = (args) => {
|
|
77
|
+
const [data, name, returnCandidate] = args;
|
|
78
|
+
if (args.length < 2 || args.length > 4 || typeof name !== "string") return void 0;
|
|
79
|
+
const hasReturnField = args.length > 2 && typeof returnCandidate === "string" && checkIsSurveyFieldName(returnCandidate);
|
|
80
|
+
const filterIndex = hasReturnField ? 3 : 2;
|
|
81
|
+
if (args.length > filterIndex) return void 0;
|
|
82
|
+
const rows = [];
|
|
83
|
+
for (const item of getSurveyArrayItems(data)) {
|
|
84
|
+
if (typeof item !== "object" || Array.isArray(item)) continue;
|
|
85
|
+
const field = toSurveyLogicNumber(getOwnValue(item, name));
|
|
86
|
+
if (field !== void 0) rows.push({
|
|
87
|
+
field,
|
|
88
|
+
item
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
returnField: hasReturnField && returnCandidate !== name ? returnCandidate : void 0,
|
|
93
|
+
rows
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
// src/services/shared/countSurveyLogicDecimals.ts
|
|
97
|
+
const countSurveyLogicDecimals = (number) => {
|
|
98
|
+
const [mantissa = "", exponent = "0"] = number.toExponential().split("e");
|
|
99
|
+
const fraction = mantissa.split(".")[1] ?? "";
|
|
100
|
+
return Math.max(fraction.length - Number(exponent), 0);
|
|
101
|
+
};
|
|
102
|
+
// src/services/SurveyExpressionBinaryOperatorMap.ts
|
|
103
|
+
const SurveyExpressionBinaryOperatorMap = {
|
|
104
|
+
"!=": {
|
|
105
|
+
operator: "notEqual",
|
|
106
|
+
power: 3
|
|
107
|
+
},
|
|
108
|
+
"%": {
|
|
109
|
+
operator: "modulo",
|
|
110
|
+
power: 5
|
|
111
|
+
},
|
|
112
|
+
"*": {
|
|
113
|
+
operator: "multiply",
|
|
114
|
+
power: 5
|
|
115
|
+
},
|
|
116
|
+
"+": {
|
|
117
|
+
operator: "add",
|
|
118
|
+
power: 4
|
|
119
|
+
},
|
|
120
|
+
"-": {
|
|
121
|
+
operator: "subtract",
|
|
122
|
+
power: 4
|
|
123
|
+
},
|
|
124
|
+
"/": {
|
|
125
|
+
operator: "divide",
|
|
126
|
+
power: 5
|
|
127
|
+
},
|
|
128
|
+
"<": {
|
|
129
|
+
operator: "less",
|
|
130
|
+
power: 3
|
|
131
|
+
},
|
|
132
|
+
"<=": {
|
|
133
|
+
operator: "lessOrEqual",
|
|
134
|
+
power: 3
|
|
135
|
+
},
|
|
136
|
+
"=": {
|
|
137
|
+
operator: "equal",
|
|
138
|
+
power: 3
|
|
139
|
+
},
|
|
140
|
+
">": {
|
|
141
|
+
operator: "greater",
|
|
142
|
+
power: 3
|
|
143
|
+
},
|
|
144
|
+
">=": {
|
|
145
|
+
operator: "greaterOrEqual",
|
|
146
|
+
power: 3
|
|
147
|
+
},
|
|
148
|
+
"^": {
|
|
149
|
+
operator: "power",
|
|
150
|
+
power: 6,
|
|
151
|
+
rightAssociative: true
|
|
152
|
+
},
|
|
153
|
+
allof: {
|
|
154
|
+
operator: "allOf",
|
|
155
|
+
power: 3
|
|
156
|
+
},
|
|
157
|
+
and: {
|
|
158
|
+
operator: "and",
|
|
159
|
+
power: 2
|
|
160
|
+
},
|
|
161
|
+
anyof: {
|
|
162
|
+
operator: "anyOf",
|
|
163
|
+
power: 3
|
|
164
|
+
},
|
|
165
|
+
contains: {
|
|
166
|
+
operator: "contains",
|
|
167
|
+
power: 3
|
|
168
|
+
},
|
|
169
|
+
notcontains: {
|
|
170
|
+
operator: "notContains",
|
|
171
|
+
power: 3
|
|
172
|
+
},
|
|
173
|
+
or: {
|
|
174
|
+
operator: "or",
|
|
175
|
+
power: 1
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
// src/services/SurveyExpressionPrefixOperatorMap.ts
|
|
179
|
+
const SurveyExpressionPrefixOperatorMap = {
|
|
180
|
+
"-": {
|
|
181
|
+
operator: "negate",
|
|
182
|
+
power: 6
|
|
183
|
+
},
|
|
184
|
+
not: {
|
|
185
|
+
operator: "not",
|
|
186
|
+
power: 3
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
// src/services/tokenizer/checkIsSurveyExpressionWordStart.ts
|
|
190
|
+
const checkIsSurveyExpressionWordStart = (character) => /[A-Za-z_]/u.test(character);
|
|
191
|
+
// src/services/constants.ts
|
|
192
|
+
const LOWEST_POWER = 0;
|
|
193
|
+
const MAX_DECIMAL_PLACES = 100;
|
|
194
|
+
const MAX_SURVEY_EXPRESSION_LENGTH = 2e3;
|
|
195
|
+
const PUNCTUATION_CHARACTERS = [
|
|
196
|
+
"(",
|
|
197
|
+
")",
|
|
198
|
+
"[",
|
|
199
|
+
"]",
|
|
200
|
+
","
|
|
201
|
+
];
|
|
202
|
+
const STRING_QUOTES = ["'", "\""];
|
|
203
|
+
const SYMBOL_OPERATOR_TEXTS = [...Object.keys(SurveyExpressionBinaryOperatorMap), ...Object.keys(SurveyExpressionPrefixOperatorMap)].filter((operatorText) => !checkIsSurveyExpressionWordStart(operatorText.charAt(0))).toSorted((firstText, secondText) => secondText.length - firstText.length);
|
|
204
|
+
// src/services/shared/roundSurveyLogicResult.ts
|
|
205
|
+
const roundSurveyLogicResult = (result, digits) => digits > 0 ? Number(result.toFixed(Math.min(digits, 100))) : result;
|
|
206
|
+
// src/services/shared/roundSurveyLogicSum.ts
|
|
207
|
+
const roundSurveyLogicSum = (left, right, sum) => roundSurveyLogicResult(sum, Math.max(countSurveyLogicDecimals(left), countSurveyLogicDecimals(right)));
|
|
208
|
+
// src/services/shared/toSurveyLogicResult.ts
|
|
209
|
+
const toSurveyLogicResult = (result) => Number.isFinite(result) ? result : void 0;
|
|
210
|
+
// src/services/shared/sumSurveyNumbers.ts
|
|
211
|
+
const sumSurveyNumbers = (numbers) => {
|
|
212
|
+
let total = 0;
|
|
213
|
+
for (const number of numbers) total = roundSurveyLogicSum(total, number, total + number);
|
|
214
|
+
return toSurveyLogicResult(total);
|
|
215
|
+
};
|
|
216
|
+
// src/services/functions/avgInArrayFunction.ts
|
|
217
|
+
const avgInArrayFunction = (args) => {
|
|
218
|
+
const params = getSurveyArrayParams(args);
|
|
219
|
+
if (params === void 0 || params.rows.length === 0) return void 0;
|
|
220
|
+
const total = sumSurveyNumbers(params.rows.map((row) => row.field));
|
|
221
|
+
return total === void 0 ? void 0 : total / params.rows.length;
|
|
222
|
+
};
|
|
223
|
+
// src/services/functions/countInArrayFunction.ts
|
|
224
|
+
const countInArrayFunction = (args) => {
|
|
225
|
+
const params = getSurveyArrayParams(args);
|
|
226
|
+
if (params === void 0) return void 0;
|
|
227
|
+
return params.rows.length;
|
|
228
|
+
};
|
|
229
|
+
// src/services/shared/getSurveyExtreme.ts
|
|
230
|
+
const getSurveyExtreme = (candidates, checkReplaces) => {
|
|
231
|
+
let best;
|
|
232
|
+
for (const candidate of candidates) if (best === void 0 || checkReplaces(best, candidate)) best = candidate;
|
|
233
|
+
return best;
|
|
234
|
+
};
|
|
235
|
+
// src/services/shared/getSurveyArrayExtreme.ts
|
|
236
|
+
const getSurveyArrayExtreme = (params, checkReplaces) => {
|
|
237
|
+
const best = getSurveyExtreme(params.rows, (bestRow, candidateRow) => checkReplaces(bestRow.field, candidateRow.field));
|
|
238
|
+
if (best === void 0) return void 0;
|
|
239
|
+
return params.returnField === void 0 ? best.field : getOwnValue(best.item, params.returnField);
|
|
240
|
+
};
|
|
241
|
+
// src/services/functions/maxInArrayFunction.ts
|
|
242
|
+
const maxInArrayFunction = (args) => {
|
|
243
|
+
const params = getSurveyArrayParams(args);
|
|
244
|
+
if (params === void 0) return void 0;
|
|
245
|
+
return getSurveyArrayExtreme(params, (best, candidate) => candidate > best);
|
|
246
|
+
};
|
|
247
|
+
// src/services/functions/minInArrayFunction.ts
|
|
248
|
+
const minInArrayFunction = (args) => {
|
|
249
|
+
const params = getSurveyArrayParams(args);
|
|
250
|
+
if (params === void 0) return void 0;
|
|
251
|
+
return getSurveyArrayExtreme(params, (best, candidate) => candidate < best);
|
|
252
|
+
};
|
|
253
|
+
// src/services/functions/sumInArrayFunction.ts
|
|
254
|
+
const sumInArrayFunction = (args) => {
|
|
255
|
+
const params = getSurveyArrayParams(args);
|
|
256
|
+
if (params === void 0) return void 0;
|
|
257
|
+
return sumSurveyNumbers(params.rows.map((row) => row.field));
|
|
258
|
+
};
|
|
259
|
+
// src/services/functions/ArrayFunctionMap.ts
|
|
260
|
+
const ArrayFunctionMap = {
|
|
261
|
+
avginarray: avgInArrayFunction,
|
|
262
|
+
countinarray: countInArrayFunction,
|
|
263
|
+
maxinarray: maxInArrayFunction,
|
|
264
|
+
mininarray: minInArrayFunction,
|
|
265
|
+
suminarray: sumInArrayFunction
|
|
266
|
+
};
|
|
267
|
+
// src/services/shared/createSurveyLogicWallClock.ts
|
|
268
|
+
const createSurveyLogicWallClock = (year, monthIndex, day, { hour = 0, millisecond = 0, minute = 0, second = 0 } = {}) => {
|
|
269
|
+
const wallClock = /* @__PURE__ */ new Date(0);
|
|
270
|
+
wallClock.setUTCFullYear(year, monthIndex, day);
|
|
271
|
+
wallClock.setUTCHours(hour, minute, second, millisecond);
|
|
272
|
+
return wallClock;
|
|
273
|
+
};
|
|
274
|
+
// src/services/shared/addSurveyLogicMonths.ts
|
|
275
|
+
const addSurveyLogicMonths = (wallClock, months) => {
|
|
276
|
+
const monthIndex = wallClock.getUTCFullYear() * 12 + wallClock.getUTCMonth() + months;
|
|
277
|
+
const year = Math.floor(monthIndex / 12);
|
|
278
|
+
const month = monthIndex - year * 12;
|
|
279
|
+
const lastDay = createSurveyLogicWallClock(year, month + 1, 0);
|
|
280
|
+
wallClock.setUTCFullYear(year, month, Math.min(wallClock.getUTCDate(), lastDay.getUTCDate()));
|
|
281
|
+
};
|
|
282
|
+
// src/services/shared/getSurveyLogicDayNumber.ts
|
|
283
|
+
const MILLISECONDS_PER_DAY = 864e5;
|
|
284
|
+
const getSurveyLogicDayNumber = (wallClock) => Math.floor(wallClock.getTime() / MILLISECONDS_PER_DAY);
|
|
285
|
+
// src/services/shared/getDateDifference.ts
|
|
286
|
+
const millisecondsPerUnitMap = {
|
|
287
|
+
hours: 36e5,
|
|
288
|
+
minutes: 6e4,
|
|
289
|
+
seconds: MILLISECONDS_PER_SECOND
|
|
290
|
+
};
|
|
291
|
+
const getDateDifference = (first, second, unit) => {
|
|
292
|
+
const [from, to] = first.getTime() <= second.getTime() ? [first, second] : [second, first];
|
|
293
|
+
if (unit === "days") return getSurveyLogicDayNumber(to) - getSurveyLogicDayNumber(from);
|
|
294
|
+
const milliseconds = getOwnValue(millisecondsPerUnitMap, unit);
|
|
295
|
+
if (milliseconds !== void 0) return Math.ceil((to.getTime() - from.getTime()) / milliseconds);
|
|
296
|
+
if (unit !== "months" && unit !== "years") return void 0;
|
|
297
|
+
const monthsApart = (to.getUTCFullYear() - from.getUTCFullYear()) * 12 + to.getUTCMonth() - from.getUTCMonth();
|
|
298
|
+
const anchor = new Date(from);
|
|
299
|
+
addSurveyLogicMonths(anchor, monthsApart);
|
|
300
|
+
const wholeMonths = anchor.getTime() > to.getTime() ? monthsApart - 1 : monthsApart;
|
|
301
|
+
return unit === "months" ? wholeMonths : Math.trunc(wholeMonths / 12);
|
|
302
|
+
};
|
|
303
|
+
// src/services/shared/checkIsSurveyLogicValueEmpty.ts
|
|
304
|
+
const checkIsSurveyLogicValueEmpty = (value) => {
|
|
305
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
306
|
+
if (typeof value === "object") return Object.values(value).every((field) => checkIsSurveyLogicValueEmpty(field));
|
|
307
|
+
return !value && value !== 0 && value !== false;
|
|
308
|
+
};
|
|
309
|
+
// src/services/shared/getSurveyOptionalArgument.ts
|
|
310
|
+
const getSurveyOptionalArgument = (value, defaultValue, readArgument) => checkIsSurveyLogicValueEmpty(value) ? defaultValue : readArgument(value);
|
|
311
|
+
// src/services/shared/getSurveyDateUnit.ts
|
|
312
|
+
const getSurveyDateUnit = (value, defaultUnit) => getSurveyOptionalArgument(value, defaultUnit, (text) => typeof text === "string" ? text : void 0);
|
|
313
|
+
// src/services/shared/checkIsCalendarDay.ts
|
|
314
|
+
const checkIsCalendarDay = (year, month, day) => {
|
|
315
|
+
const date = createSurveyLogicWallClock(year, month - 1, day);
|
|
316
|
+
return date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
317
|
+
};
|
|
318
|
+
// src/services/shared/toSurveyLogicWallClock.ts
|
|
319
|
+
const toSurveyLogicWallClock = (instant) => createSurveyLogicWallClock(instant.getFullYear(), instant.getMonth(), instant.getDate(), {
|
|
320
|
+
hour: instant.getHours(),
|
|
321
|
+
millisecond: instant.getMilliseconds(),
|
|
322
|
+
minute: instant.getMinutes(),
|
|
323
|
+
second: instant.getSeconds()
|
|
324
|
+
});
|
|
325
|
+
// src/services/shared/parseSurveyLogicDate.ts
|
|
326
|
+
const DATE_TEXT_REGEX = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})(?:[T ](?<hour>\d{2}):(?<minute>\d{2})(?::(?<second>\d{2})(?:\.(?<fraction>\d+))?)?(?<zone>Z|[+-]\d{2}:\d{2})?)?$/u;
|
|
327
|
+
const parseSurveyLogicDate = (value) => {
|
|
328
|
+
if (typeof value !== "string") return void 0;
|
|
329
|
+
const groups = DATE_TEXT_REGEX.exec(value)?.groups;
|
|
330
|
+
if (groups === void 0) return void 0;
|
|
331
|
+
const { day: dayText, fraction: fractionText = "", hour: hourText = "0", minute: minuteText = "0", month: monthText, second: secondText = "0", year: yearText, zone } = groups;
|
|
332
|
+
const year = Number(yearText);
|
|
333
|
+
const month = Number(monthText);
|
|
334
|
+
const day = Number(dayText);
|
|
335
|
+
const hour = Number(hourText);
|
|
336
|
+
const minute = Number(minuteText);
|
|
337
|
+
const second = Number(secondText);
|
|
338
|
+
if (!checkIsCalendarDay(year, month, day) || hour > 23 || minute > 59 || second > 59) return void 0;
|
|
339
|
+
const millisecond = Number(`${fractionText}00`.slice(0, 3));
|
|
340
|
+
if (zone !== void 0) {
|
|
341
|
+
const instant = /* @__PURE__ */ new Date(`${yearText}-${monthText}-${dayText}T${hourText}:${minuteText}:${secondText}.${padWithZeros(millisecond, 3)}${zone}`);
|
|
342
|
+
return Number.isNaN(instant.getTime()) ? void 0 : toSurveyLogicWallClock(instant);
|
|
343
|
+
}
|
|
344
|
+
return createSurveyLogicWallClock(year, month - 1, day, {
|
|
345
|
+
hour,
|
|
346
|
+
millisecond,
|
|
347
|
+
minute,
|
|
348
|
+
second
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
// src/services/functions/ageFunction.ts
|
|
352
|
+
const ageFunction = ([birthdateValue, intervalValue], context) => {
|
|
353
|
+
const birthdate = parseSurveyLogicDate(birthdateValue);
|
|
354
|
+
const now = toSurveyLogicWallClock(context.now);
|
|
355
|
+
const unit = getSurveyDateUnit(intervalValue, "years");
|
|
356
|
+
if (birthdate === void 0 || birthdate.getTime() > now.getTime() || unit === void 0) return void 0;
|
|
357
|
+
return getDateDifference(birthdate, now, unit);
|
|
358
|
+
};
|
|
359
|
+
// src/services/shared/toSurveyLogicDate.ts
|
|
360
|
+
const toSurveyLogicDate = (wallClock) => {
|
|
361
|
+
if (Number.isNaN(wallClock.getTime())) return void 0;
|
|
362
|
+
const year = wallClock.getUTCFullYear();
|
|
363
|
+
if (year < 0 || year > 9999) return void 0;
|
|
364
|
+
const datePart = `${padWithZeros(year, 4)}-${padWithZeros(wallClock.getUTCMonth() + 1, 2)}-${padWithZeros(wallClock.getUTCDate(), 2)}`;
|
|
365
|
+
if (wallClock.getUTCHours() === 0 && wallClock.getUTCMinutes() === 0 && wallClock.getUTCSeconds() === 0 && wallClock.getUTCMilliseconds() === 0) return datePart;
|
|
366
|
+
return `${datePart}T${padWithZeros(wallClock.getUTCHours(), 2)}:${padWithZeros(wallClock.getUTCMinutes(), 2)}:${padWithZeros(wallClock.getUTCSeconds(), 2)}.${padWithZeros(wallClock.getUTCMilliseconds(), 3)}`;
|
|
367
|
+
};
|
|
368
|
+
// src/services/functions/dateAddFunction.ts
|
|
369
|
+
const unitAdditionMap = {
|
|
370
|
+
days: (date, amount) => {
|
|
371
|
+
date.setUTCDate(date.getUTCDate() + amount);
|
|
372
|
+
},
|
|
373
|
+
hours: (date, amount) => {
|
|
374
|
+
date.setUTCHours(date.getUTCHours() + amount);
|
|
375
|
+
},
|
|
376
|
+
minutes: (date, amount) => {
|
|
377
|
+
date.setUTCMinutes(date.getUTCMinutes() + amount);
|
|
378
|
+
},
|
|
379
|
+
months: (date, amount) => {
|
|
380
|
+
addSurveyLogicMonths(date, amount);
|
|
381
|
+
},
|
|
382
|
+
seconds: (date, amount) => {
|
|
383
|
+
date.setUTCSeconds(date.getUTCSeconds() + amount);
|
|
384
|
+
},
|
|
385
|
+
years: (date, amount) => {
|
|
386
|
+
addSurveyLogicMonths(date, amount * 12);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const dateAddFunction = ([dateValue, amountValue, intervalValue]) => {
|
|
390
|
+
const date = parseSurveyLogicDate(dateValue);
|
|
391
|
+
const amount = toSurveyLogicNumber(amountValue);
|
|
392
|
+
const unit = getSurveyDateUnit(intervalValue, "days");
|
|
393
|
+
const addToDate = unit === void 0 ? void 0 : getOwnValue(unitAdditionMap, unit);
|
|
394
|
+
if (date === void 0 || amount === void 0 || addToDate === void 0) return void 0;
|
|
395
|
+
addToDate(date, Math.trunc(amount));
|
|
396
|
+
return toSurveyLogicDate(date);
|
|
397
|
+
};
|
|
398
|
+
// src/services/functions/dateDiffFunction.ts
|
|
399
|
+
const dateDiffFunction = ([fromValue, toValue, intervalValue]) => {
|
|
400
|
+
const from = parseSurveyLogicDate(fromValue);
|
|
401
|
+
const to = parseSurveyLogicDate(toValue);
|
|
402
|
+
const unit = getSurveyDateUnit(intervalValue, "days");
|
|
403
|
+
if (from === void 0 || to === void 0 || unit === void 0) return void 0;
|
|
404
|
+
return getDateDifference(from, to, unit);
|
|
405
|
+
};
|
|
406
|
+
// src/services/shared/getDateOrNow.ts
|
|
407
|
+
const getDateOrNow = (value, now) => getSurveyOptionalArgument(value, toSurveyLogicWallClock(now), parseSurveyLogicDate);
|
|
408
|
+
// src/services/functions/dayFunction.ts
|
|
409
|
+
const dayFunction = (args, context) => getDateOrNow(args[0], context.now)?.getUTCDate();
|
|
410
|
+
// src/services/functions/monthFunction.ts
|
|
411
|
+
const monthFunction = (args, context) => {
|
|
412
|
+
const date = getDateOrNow(args[0], context.now);
|
|
413
|
+
return date === void 0 ? void 0 : date.getUTCMonth() + 1;
|
|
414
|
+
};
|
|
415
|
+
// src/services/shared/getSurveyNumberWithPrecision.ts
|
|
416
|
+
const getSurveyNumberWithPrecision = (args) => {
|
|
417
|
+
const [value, precisionValue] = args;
|
|
418
|
+
const number = toSurveyLogicNumber(value);
|
|
419
|
+
const precision = getSurveyOptionalArgument(precisionValue, 0, toSurveyLogicNumber);
|
|
420
|
+
if (number === void 0 || precision === void 0) return void 0;
|
|
421
|
+
if (!Number.isInteger(precision) || precision < 0 || precision > 100) return void 0;
|
|
422
|
+
return [number, precision];
|
|
423
|
+
};
|
|
424
|
+
// src/services/functions/roundFunction.ts
|
|
425
|
+
const roundFunction = (args) => {
|
|
426
|
+
const numberWithPrecision = getSurveyNumberWithPrecision(args);
|
|
427
|
+
if (numberWithPrecision === void 0) return void 0;
|
|
428
|
+
const [number, precision] = numberWithPrecision;
|
|
429
|
+
const scale = 10 ** precision;
|
|
430
|
+
return toSurveyLogicResult(Math.round(number * scale * (1 + Number.EPSILON)) / scale);
|
|
431
|
+
};
|
|
432
|
+
// src/services/functions/todayFunction.ts
|
|
433
|
+
const todayFunction = (args, context) => {
|
|
434
|
+
const date = toSurveyLogicWallClock(context.now);
|
|
435
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
436
|
+
if (args.length === 1) {
|
|
437
|
+
const days = getSurveyOptionalArgument(args[0], 0, toSurveyLogicNumber);
|
|
438
|
+
if (days === void 0) return void 0;
|
|
439
|
+
date.setUTCDate(date.getUTCDate() + Math.trunc(days));
|
|
440
|
+
}
|
|
441
|
+
return toSurveyLogicDate(date);
|
|
442
|
+
};
|
|
443
|
+
// src/services/functions/truncFunction.ts
|
|
444
|
+
const PRODUCT_DIGITS = 15;
|
|
445
|
+
const truncFunction = (args) => {
|
|
446
|
+
const numberWithPrecision = getSurveyNumberWithPrecision(args);
|
|
447
|
+
if (numberWithPrecision === void 0) return void 0;
|
|
448
|
+
const [number, precision] = numberWithPrecision;
|
|
449
|
+
if (precision === 0) return Math.trunc(number);
|
|
450
|
+
if (countSurveyLogicDecimals(number) <= precision) return number;
|
|
451
|
+
const scale = 10 ** precision;
|
|
452
|
+
return Math.trunc(Number((number * scale).toPrecision(PRODUCT_DIGITS))) / scale;
|
|
453
|
+
};
|
|
454
|
+
// src/services/functions/weekdayFunction.ts
|
|
455
|
+
const weekdayFunction = (args, context) => getDateOrNow(args[0], context.now)?.getUTCDay();
|
|
456
|
+
// src/services/functions/yearFunction.ts
|
|
457
|
+
const yearFunction = (args, context) => getDateOrNow(args[0], context.now)?.getUTCFullYear();
|
|
458
|
+
// src/services/functions/DateFunctionMap.ts
|
|
459
|
+
const DateFunctionMap = {
|
|
460
|
+
age: ageFunction,
|
|
461
|
+
dateadd: dateAddFunction,
|
|
462
|
+
datediff: dateDiffFunction,
|
|
463
|
+
day: dayFunction,
|
|
464
|
+
month: monthFunction,
|
|
465
|
+
round: roundFunction,
|
|
466
|
+
today: todayFunction,
|
|
467
|
+
trunc: truncFunction,
|
|
468
|
+
weekday: weekdayFunction,
|
|
469
|
+
year: yearFunction
|
|
470
|
+
};
|
|
471
|
+
// src/services/shared/flattenSurveyNumbers.ts
|
|
472
|
+
const flattenSurveyNumbers = (values) => values.flatMap((value) => {
|
|
473
|
+
if (Array.isArray(value)) return flattenSurveyNumbers(value);
|
|
474
|
+
const number = toSurveyLogicNumber(value);
|
|
475
|
+
return number === void 0 ? [] : [number];
|
|
476
|
+
});
|
|
477
|
+
// src/services/functions/avgFunction.ts
|
|
478
|
+
const avgFunction = (args) => {
|
|
479
|
+
const numbers = flattenSurveyNumbers(args);
|
|
480
|
+
if (numbers.length === 0) return void 0;
|
|
481
|
+
const total = sumSurveyNumbers(numbers);
|
|
482
|
+
return total === void 0 ? void 0 : total / numbers.length;
|
|
483
|
+
};
|
|
484
|
+
// src/services/functions/countFunction.ts
|
|
485
|
+
const countFunction = (args) => {
|
|
486
|
+
return flattenSurveyNumbers(args).length;
|
|
487
|
+
};
|
|
488
|
+
// src/services/functions/iifFunction.ts
|
|
489
|
+
const iifFunction = (args) => {
|
|
490
|
+
if (args.length < 2) return void 0;
|
|
491
|
+
const [condition, valueIfTrue, valueIfFalse] = args;
|
|
492
|
+
return condition === true ? valueIfTrue : valueIfFalse;
|
|
493
|
+
};
|
|
494
|
+
// src/services/functions/maxFunction.ts
|
|
495
|
+
const maxFunction = (args) => getSurveyExtreme(flattenSurveyNumbers(args), (best, candidate) => candidate > best);
|
|
496
|
+
// src/services/functions/minFunction.ts
|
|
497
|
+
const minFunction = (args) => getSurveyExtreme(flattenSurveyNumbers(args), (best, candidate) => candidate < best);
|
|
498
|
+
// src/services/shared/getSurveyTextIndex.ts
|
|
499
|
+
const getSurveyTextIndex = (index, length) => Math.min(Math.max(Math.trunc(index), 0), length);
|
|
500
|
+
// src/services/shared/toSurveyLogicText.ts
|
|
501
|
+
const toSurveyLogicText = (value) => {
|
|
502
|
+
if (value === void 0) return "";
|
|
503
|
+
if (Array.isArray(value)) return value.map((item) => toSurveyLogicText(item)).join(",");
|
|
504
|
+
if (typeof value === "object") return "";
|
|
505
|
+
return String(value);
|
|
506
|
+
};
|
|
507
|
+
// src/services/functions/substringFunction.ts
|
|
508
|
+
const substringFunction = (args) => {
|
|
509
|
+
const [textValue, start, end] = args;
|
|
510
|
+
const startNumber = toSurveyLogicNumber(start);
|
|
511
|
+
if (textValue === void 0 || startNumber === void 0) return void 0;
|
|
512
|
+
const text = toSurveyLogicText(textValue);
|
|
513
|
+
const endNumber = getSurveyOptionalArgument(end, text.length, toSurveyLogicNumber);
|
|
514
|
+
if (endNumber === void 0) return void 0;
|
|
515
|
+
const startIndex = getSurveyTextIndex(startNumber, text.length);
|
|
516
|
+
const endIndex = getSurveyTextIndex(endNumber, text.length);
|
|
517
|
+
return text.slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex));
|
|
518
|
+
};
|
|
519
|
+
// src/services/functions/sumFunction.ts
|
|
520
|
+
const sumFunction = (args) => sumSurveyNumbers(flattenSurveyNumbers(args));
|
|
521
|
+
// src/services/functions/GeneralFunctionMap.ts
|
|
522
|
+
const GeneralFunctionMap = {
|
|
523
|
+
avg: avgFunction,
|
|
524
|
+
count: countFunction,
|
|
525
|
+
iif: iifFunction,
|
|
526
|
+
max: maxFunction,
|
|
527
|
+
min: minFunction,
|
|
528
|
+
substring: substringFunction,
|
|
529
|
+
sum: sumFunction
|
|
530
|
+
};
|
|
531
|
+
// src/services/functions/SurveyLogicFunctionMap.ts
|
|
532
|
+
const SurveyLogicFunctionMap = {
|
|
533
|
+
...ArrayFunctionMap,
|
|
534
|
+
...DateFunctionMap,
|
|
535
|
+
...GeneralFunctionMap
|
|
536
|
+
};
|
|
537
|
+
// src/services/getCallRefusal.ts
|
|
538
|
+
const getCallRefusal = (node) => {
|
|
539
|
+
const name = node.name.toLowerCase();
|
|
540
|
+
if (getOwnValue(SurveyLogicFunctionMap, name) === void 0) return `Unknown function "${node.name}"`;
|
|
541
|
+
if (getOwnValue(ArrayFunctionMap, name) === void 0 || node.args.length < 3) return void 0;
|
|
542
|
+
const third = node.args.at(2);
|
|
543
|
+
return node.args.length === 3 && third?.type === "literal" && typeof third.value === "string" && checkIsSurveyFieldName(third.value) ? void 0 : `The filter form of "${node.name}" is not supported`;
|
|
544
|
+
};
|
|
545
|
+
// src/services/getSurveyExpressionChildren.ts
|
|
546
|
+
const getSurveyExpressionChildren = (node) => {
|
|
547
|
+
switch (node.type) {
|
|
548
|
+
case "binary": return [node.left, node.right];
|
|
549
|
+
case "call": return node.args;
|
|
550
|
+
case "list": return node.items;
|
|
551
|
+
case "literal":
|
|
552
|
+
case "reference": return [];
|
|
553
|
+
case "unary": return [node.operand];
|
|
554
|
+
default: return exhaustiveGuard(node);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
// src/services/findSurveyExpressionRefusal.ts
|
|
558
|
+
const findSurveyExpressionRefusal = (node) => {
|
|
559
|
+
if (node.type === "call") {
|
|
560
|
+
const refusal = getCallRefusal(node);
|
|
561
|
+
if (refusal !== void 0) return {
|
|
562
|
+
message: refusal,
|
|
563
|
+
position: node.position
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
for (const child of getSurveyExpressionChildren(node)) {
|
|
567
|
+
const refusal = findSurveyExpressionRefusal(child);
|
|
568
|
+
if (refusal !== void 0) return refusal;
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
// src/services/SurveyExpressionPostfixOperatorMap.ts
|
|
572
|
+
const SurveyExpressionPostfixOperatorMap = {
|
|
573
|
+
empty: {
|
|
574
|
+
operator: "empty",
|
|
575
|
+
power: 3
|
|
576
|
+
},
|
|
577
|
+
notempty: {
|
|
578
|
+
operator: "notEmpty",
|
|
579
|
+
power: 3
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
// src/services/parser/parseBinaryOperators.ts
|
|
583
|
+
const parseBinaryOperators = (state, left, minimumPower, parse) => {
|
|
584
|
+
const token = takeOne(state.tokens, state.index);
|
|
585
|
+
if (token.type !== "operator") return ok(left);
|
|
586
|
+
const postfix = getOwnValue(SurveyExpressionPostfixOperatorMap, token.text);
|
|
587
|
+
if (postfix !== void 0 && postfix.power >= minimumPower) {
|
|
588
|
+
state.index++;
|
|
589
|
+
const unary = {
|
|
590
|
+
operand: left,
|
|
591
|
+
operator: postfix.operator,
|
|
592
|
+
position: token.position,
|
|
593
|
+
type: "unary"
|
|
594
|
+
};
|
|
595
|
+
return parseBinaryOperators(state, unary, minimumPower, parse);
|
|
596
|
+
}
|
|
597
|
+
const binary = getOwnValue(SurveyExpressionBinaryOperatorMap, token.text);
|
|
598
|
+
if (binary === void 0 || binary.power < minimumPower) return ok(left);
|
|
599
|
+
state.index++;
|
|
600
|
+
return parse(state, binary.rightAssociative ? binary.power : binary.power + 1).andThen((right) => {
|
|
601
|
+
const node = {
|
|
602
|
+
left,
|
|
603
|
+
operator: binary.operator,
|
|
604
|
+
position: token.position,
|
|
605
|
+
right,
|
|
606
|
+
type: "binary"
|
|
607
|
+
};
|
|
608
|
+
return parseBinaryOperators(state, node, minimumPower, parse);
|
|
609
|
+
});
|
|
610
|
+
};
|
|
611
|
+
// src/services/parser/checkIsPunctuation.ts
|
|
612
|
+
const checkIsPunctuation = (token, text) => token.type === "punctuation" && token.text === text;
|
|
613
|
+
// src/services/parser/parseSequenceItems.ts
|
|
614
|
+
const parseSequenceItems = (state, closing, message, parse, items) => parse(state, 0).andThen((item) => {
|
|
615
|
+
items.push(item);
|
|
616
|
+
const token = takeOne(state.tokens, state.index);
|
|
617
|
+
if (checkIsPunctuation(token, ",")) {
|
|
618
|
+
state.index++;
|
|
619
|
+
return parseSequenceItems(state, closing, message, parse, items);
|
|
620
|
+
}
|
|
621
|
+
if (checkIsPunctuation(token, closing)) {
|
|
622
|
+
state.index++;
|
|
623
|
+
return ok(items);
|
|
624
|
+
}
|
|
625
|
+
return err({
|
|
626
|
+
message,
|
|
627
|
+
position: token.position
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
// src/services/parser/parseSequence.ts
|
|
631
|
+
const parseSequence = (state, closing, message, parse) => {
|
|
632
|
+
if (checkIsPunctuation(takeOne(state.tokens, state.index), closing)) {
|
|
633
|
+
state.index++;
|
|
634
|
+
return ok([]);
|
|
635
|
+
}
|
|
636
|
+
return parseSequenceItems(state, closing, message, parse, []);
|
|
637
|
+
};
|
|
638
|
+
// src/services/parser/parseCall.ts
|
|
639
|
+
const parseCall = (state, parse) => {
|
|
640
|
+
const name = takeOne(state.tokens, state.index);
|
|
641
|
+
state.index++;
|
|
642
|
+
const opening = takeOne(state.tokens, state.index);
|
|
643
|
+
const message = `Expected ( after ${name.text}`;
|
|
644
|
+
if (!checkIsPunctuation(opening, "(")) return err({
|
|
645
|
+
message,
|
|
646
|
+
position: opening.position
|
|
647
|
+
});
|
|
648
|
+
state.index++;
|
|
649
|
+
return parseSequence(state, ")", "Expected , or ) to close the call opened here", parse).map((args) => ({
|
|
650
|
+
args,
|
|
651
|
+
name: name.text,
|
|
652
|
+
position: name.position,
|
|
653
|
+
type: "call"
|
|
654
|
+
}));
|
|
655
|
+
};
|
|
656
|
+
// src/services/parser/parseGroup.ts
|
|
657
|
+
const parseGroup = (state, parse) => {
|
|
658
|
+
state.index++;
|
|
659
|
+
return parse(state, 0).andThen((node) => {
|
|
660
|
+
const token = takeOne(state.tokens, state.index);
|
|
661
|
+
if (checkIsPunctuation(token, ")")) {
|
|
662
|
+
state.index++;
|
|
663
|
+
return ok(node);
|
|
664
|
+
}
|
|
665
|
+
return err({
|
|
666
|
+
message: "Expected ) to close the parenthesis opened here",
|
|
667
|
+
position: token.position
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
};
|
|
671
|
+
// src/services/parser/parseList.ts
|
|
672
|
+
const parseList = (state, parse) => {
|
|
673
|
+
const opening = takeOne(state.tokens, state.index);
|
|
674
|
+
state.index++;
|
|
675
|
+
return parseSequence(state, "]", "Expected , or ] to close the list opened here", parse).map((items) => ({
|
|
676
|
+
items,
|
|
677
|
+
position: opening.position,
|
|
678
|
+
type: "list"
|
|
679
|
+
}));
|
|
680
|
+
};
|
|
681
|
+
// src/services/parser/parsePrefixOperator.ts
|
|
682
|
+
const parsePrefixOperator = (state, parse) => {
|
|
683
|
+
const token = takeOne(state.tokens, state.index);
|
|
684
|
+
const prefix = getOwnValue(SurveyExpressionPrefixOperatorMap, token.text);
|
|
685
|
+
if (prefix === void 0) return err({
|
|
686
|
+
message: "Expected an expression",
|
|
687
|
+
position: token.position
|
|
688
|
+
});
|
|
689
|
+
state.index++;
|
|
690
|
+
return parse(state, prefix.power).map((operand) => ({
|
|
691
|
+
operand,
|
|
692
|
+
operator: prefix.operator,
|
|
693
|
+
position: token.position,
|
|
694
|
+
type: "unary"
|
|
695
|
+
}));
|
|
696
|
+
};
|
|
697
|
+
// src/services/parser/parseOperand.ts
|
|
698
|
+
const parseOperand = (state, parse) => {
|
|
699
|
+
const token = takeOne(state.tokens, state.index);
|
|
700
|
+
switch (token.type) {
|
|
701
|
+
case "boolean":
|
|
702
|
+
state.index++;
|
|
703
|
+
return ok({
|
|
704
|
+
position: token.position,
|
|
705
|
+
type: "literal",
|
|
706
|
+
value: token.text === "true"
|
|
707
|
+
});
|
|
708
|
+
case "end": break;
|
|
709
|
+
case "identifier": return parseCall(state, parse);
|
|
710
|
+
case "number":
|
|
711
|
+
state.index++;
|
|
712
|
+
return ok({
|
|
713
|
+
position: token.position,
|
|
714
|
+
type: "literal",
|
|
715
|
+
value: Number(token.text)
|
|
716
|
+
});
|
|
717
|
+
case "operator": return parsePrefixOperator(state, parse);
|
|
718
|
+
case "punctuation":
|
|
719
|
+
if (checkIsPunctuation(token, "(")) return parseGroup(state, parse);
|
|
720
|
+
if (checkIsPunctuation(token, "[")) return parseList(state, parse);
|
|
721
|
+
break;
|
|
722
|
+
case "reference":
|
|
723
|
+
state.index++;
|
|
724
|
+
return ok({
|
|
725
|
+
name: token.text,
|
|
726
|
+
position: token.position,
|
|
727
|
+
type: "reference"
|
|
728
|
+
});
|
|
729
|
+
case "string":
|
|
730
|
+
state.index++;
|
|
731
|
+
return ok({
|
|
732
|
+
position: token.position,
|
|
733
|
+
type: "literal",
|
|
734
|
+
value: token.text
|
|
735
|
+
});
|
|
736
|
+
default: return exhaustiveGuard(token.type);
|
|
737
|
+
}
|
|
738
|
+
return err({
|
|
739
|
+
message: "Expected an expression",
|
|
740
|
+
position: token.position
|
|
741
|
+
});
|
|
742
|
+
};
|
|
743
|
+
// src/services/parser/parseBinary.ts
|
|
744
|
+
const parseBinary = (state, minimumPower) => parseOperand(state, parseBinary).andThen((left) => parseBinaryOperators(state, left, minimumPower, parseBinary));
|
|
745
|
+
// src/services/tokenizer/advanceSurveyExpressionWhile.ts
|
|
746
|
+
const advanceSurveyExpressionWhile = (state, checkCharacter) => {
|
|
747
|
+
while (state.index < state.source.length && checkCharacter(state.source.charAt(state.index))) state.index++;
|
|
748
|
+
};
|
|
749
|
+
// src/services/tokenizer/checkIsSurveyExpressionWhitespace.ts
|
|
750
|
+
const checkIsSurveyExpressionWhitespace = (character) => /\s/u.test(character);
|
|
751
|
+
// src/services/tokenizer/checkIsSurveyExpressionDigit.ts
|
|
752
|
+
const checkIsSurveyExpressionDigit = (character) => /\d/u.test(character);
|
|
753
|
+
// src/services/tokenizer/readSurveyExpressionNumber.ts
|
|
754
|
+
const readSurveyExpressionNumber = (state) => {
|
|
755
|
+
const start = state.index;
|
|
756
|
+
advanceSurveyExpressionWhile(state, checkIsSurveyExpressionDigit);
|
|
757
|
+
if (state.source.charAt(state.index) === "." && checkIsSurveyExpressionDigit(state.source.charAt(state.index + 1))) {
|
|
758
|
+
state.index++;
|
|
759
|
+
advanceSurveyExpressionWhile(state, checkIsSurveyExpressionDigit);
|
|
760
|
+
}
|
|
761
|
+
state.tokens.push({
|
|
762
|
+
position: start,
|
|
763
|
+
text: state.source.slice(start, state.index),
|
|
764
|
+
type: "number"
|
|
765
|
+
});
|
|
766
|
+
};
|
|
767
|
+
// src/services/tokenizer/readSurveyExpressionReference.ts
|
|
768
|
+
const readSurveyExpressionReference = (state) => {
|
|
769
|
+
const start = state.index;
|
|
770
|
+
const closing = state.source.indexOf("}", start);
|
|
771
|
+
if (closing === -1) {
|
|
772
|
+
state.error = {
|
|
773
|
+
message: "Expected } to close the reference opened here",
|
|
774
|
+
position: state.source.length
|
|
775
|
+
};
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const name = state.source.slice(start + 1, closing);
|
|
779
|
+
if (name.trim() === "") {
|
|
780
|
+
state.error = {
|
|
781
|
+
message: "Expected a question name between { and }",
|
|
782
|
+
position: closing
|
|
783
|
+
};
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
state.index = closing + 1;
|
|
787
|
+
state.tokens.push({
|
|
788
|
+
position: start,
|
|
789
|
+
text: name,
|
|
790
|
+
type: "reference"
|
|
791
|
+
});
|
|
792
|
+
};
|
|
793
|
+
// src/services/tokenizer/readSurveyExpressionString.ts
|
|
794
|
+
const readSurveyExpressionString = (state) => {
|
|
795
|
+
const start = state.index;
|
|
796
|
+
const quote = state.source.charAt(start);
|
|
797
|
+
let text = "";
|
|
798
|
+
state.index++;
|
|
799
|
+
while (state.index < state.source.length && state.source.charAt(state.index) !== quote) {
|
|
800
|
+
const character = state.source.charAt(state.index);
|
|
801
|
+
const nextCharacter = state.source.charAt(state.index + 1);
|
|
802
|
+
if (character === "\\" && ["\\", quote].includes(nextCharacter)) {
|
|
803
|
+
text += nextCharacter;
|
|
804
|
+
state.index += 2;
|
|
805
|
+
} else {
|
|
806
|
+
text += character;
|
|
807
|
+
state.index++;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (state.index >= state.source.length) {
|
|
811
|
+
state.error = {
|
|
812
|
+
message: `Expected ${quote} to close the string opened here`,
|
|
813
|
+
position: state.source.length
|
|
814
|
+
};
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
state.index++;
|
|
818
|
+
state.tokens.push({
|
|
819
|
+
position: start,
|
|
820
|
+
text,
|
|
821
|
+
type: "string"
|
|
822
|
+
});
|
|
823
|
+
};
|
|
824
|
+
// src/services/tokenizer/readSurveyExpressionSymbol.ts
|
|
825
|
+
const readSurveyExpressionSymbol = (state) => {
|
|
826
|
+
const character = state.source.charAt(state.index);
|
|
827
|
+
if (PUNCTUATION_CHARACTERS.includes(character)) {
|
|
828
|
+
state.tokens.push({
|
|
829
|
+
position: state.index,
|
|
830
|
+
text: character,
|
|
831
|
+
type: "punctuation"
|
|
832
|
+
});
|
|
833
|
+
state.index++;
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
const symbol = SYMBOL_OPERATOR_TEXTS.find((symbolText) => state.source.startsWith(symbolText, state.index));
|
|
837
|
+
if (symbol === void 0) {
|
|
838
|
+
state.error = {
|
|
839
|
+
message: `Unexpected character ${character}`,
|
|
840
|
+
position: state.index
|
|
841
|
+
};
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
state.tokens.push({
|
|
845
|
+
position: state.index,
|
|
846
|
+
text: symbol,
|
|
847
|
+
type: "operator"
|
|
848
|
+
});
|
|
849
|
+
state.index += symbol.length;
|
|
850
|
+
};
|
|
851
|
+
// src/services/tokenizer/checkIsSurveyExpressionKeyword.ts
|
|
852
|
+
const checkIsSurveyExpressionKeyword = (lowercasedWord) => getOwnValue(SurveyExpressionBinaryOperatorMap, lowercasedWord) !== void 0 || getOwnValue(SurveyExpressionPostfixOperatorMap, lowercasedWord) !== void 0 || getOwnValue(SurveyExpressionPrefixOperatorMap, lowercasedWord) !== void 0;
|
|
853
|
+
// src/services/tokenizer/checkIsSurveyExpressionWordCharacter.ts
|
|
854
|
+
const checkIsSurveyExpressionWordCharacter = (character) => /[\dA-Za-z_]/u.test(character);
|
|
855
|
+
// src/services/tokenizer/readSurveyExpressionWord.ts
|
|
856
|
+
const readSurveyExpressionWord = (state) => {
|
|
857
|
+
const start = state.index;
|
|
858
|
+
advanceSurveyExpressionWhile(state, checkIsSurveyExpressionWordCharacter);
|
|
859
|
+
const word = state.source.slice(start, state.index);
|
|
860
|
+
const lowercasedWord = word.toLowerCase();
|
|
861
|
+
if (["false", "true"].includes(lowercasedWord)) state.tokens.push({
|
|
862
|
+
position: start,
|
|
863
|
+
text: lowercasedWord,
|
|
864
|
+
type: "boolean"
|
|
865
|
+
});
|
|
866
|
+
else if (checkIsSurveyExpressionKeyword(lowercasedWord)) state.tokens.push({
|
|
867
|
+
position: start,
|
|
868
|
+
text: lowercasedWord,
|
|
869
|
+
type: "operator"
|
|
870
|
+
});
|
|
871
|
+
else state.tokens.push({
|
|
872
|
+
position: start,
|
|
873
|
+
text: word,
|
|
874
|
+
type: "identifier"
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
// src/services/tokenizer/readSurveyExpressionToken.ts
|
|
878
|
+
const readSurveyExpressionToken = (state) => {
|
|
879
|
+
const character = state.source.charAt(state.index);
|
|
880
|
+
const nextCharacter = state.source.charAt(state.index + 1);
|
|
881
|
+
if (checkIsSurveyExpressionDigit(character) || character === "." && checkIsSurveyExpressionDigit(nextCharacter)) readSurveyExpressionNumber(state);
|
|
882
|
+
else if (STRING_QUOTES.includes(character)) readSurveyExpressionString(state);
|
|
883
|
+
else if (character === "{") readSurveyExpressionReference(state);
|
|
884
|
+
else if (checkIsSurveyExpressionWordStart(character)) readSurveyExpressionWord(state);
|
|
885
|
+
else readSurveyExpressionSymbol(state);
|
|
886
|
+
};
|
|
887
|
+
// src/services/tokenizeSurveyExpression.ts
|
|
888
|
+
const tokenizeSurveyExpression = (source) => {
|
|
889
|
+
const state = {
|
|
890
|
+
index: 0,
|
|
891
|
+
source,
|
|
892
|
+
tokens: []
|
|
893
|
+
};
|
|
894
|
+
advanceSurveyExpressionWhile(state, checkIsSurveyExpressionWhitespace);
|
|
895
|
+
while (state.error === void 0 && state.index < source.length) {
|
|
896
|
+
readSurveyExpressionToken(state);
|
|
897
|
+
advanceSurveyExpressionWhile(state, checkIsSurveyExpressionWhitespace);
|
|
898
|
+
}
|
|
899
|
+
if (state.error !== void 0) return err(state.error);
|
|
900
|
+
state.tokens.push({
|
|
901
|
+
position: source.length,
|
|
902
|
+
text: "",
|
|
903
|
+
type: "end"
|
|
904
|
+
});
|
|
905
|
+
return ok(state.tokens);
|
|
906
|
+
};
|
|
907
|
+
// src/services/parseSurveyExpression.ts
|
|
908
|
+
const parseSurveyExpression = (source) => {
|
|
909
|
+
if (source.length > 2e3) return err({
|
|
910
|
+
message: "The expression is too long",
|
|
911
|
+
position: MAX_SURVEY_EXPRESSION_LENGTH
|
|
912
|
+
});
|
|
913
|
+
return tokenizeSurveyExpression(source).andThen((tokens) => {
|
|
914
|
+
const state = {
|
|
915
|
+
index: 0,
|
|
916
|
+
tokens
|
|
917
|
+
};
|
|
918
|
+
return parseBinary(state, 0).andThen((node) => {
|
|
919
|
+
const token = takeOne(tokens, state.index);
|
|
920
|
+
return token.type === "end" ? ok(node) : err({
|
|
921
|
+
message: "Expected the end of the expression",
|
|
922
|
+
position: token.position
|
|
923
|
+
});
|
|
924
|
+
});
|
|
925
|
+
});
|
|
926
|
+
};
|
|
927
|
+
// src/services/checkSurveyExpression.ts
|
|
928
|
+
const checkSurveyExpression = (source) => parseSurveyExpression(source).match(findSurveyExpressionRefusal, (error) => error);
|
|
929
|
+
// src/services/shared/roundSurveyLogicProduct.ts
|
|
930
|
+
const roundSurveyLogicProduct = (left, right, product) => roundSurveyLogicResult(product, countSurveyLogicDecimals(left) + countSurveyLogicDecimals(right));
|
|
931
|
+
// src/services/evaluator/operators/evaluateArithmeticOperator.ts
|
|
932
|
+
const evaluateArithmeticOperator = (operator, left, right) => {
|
|
933
|
+
const leftNumber = toSurveyLogicNumber(left);
|
|
934
|
+
const rightNumber = toSurveyLogicNumber(right);
|
|
935
|
+
if (leftNumber === void 0 || rightNumber === void 0) return operator === "add" && leftNumber === void 0 && rightNumber === void 0 && typeof left === "string" && typeof right === "string" && left !== "" && right !== "" ? left + right : void 0;
|
|
936
|
+
switch (operator) {
|
|
937
|
+
case "add": return toSurveyLogicResult(roundSurveyLogicSum(leftNumber, rightNumber, leftNumber + rightNumber));
|
|
938
|
+
case "divide": return rightNumber === 0 ? void 0 : toSurveyLogicResult(leftNumber / rightNumber);
|
|
939
|
+
case "modulo": return rightNumber === 0 ? void 0 : toSurveyLogicResult(leftNumber % rightNumber);
|
|
940
|
+
case "multiply": return toSurveyLogicResult(roundSurveyLogicProduct(leftNumber, rightNumber, leftNumber * rightNumber));
|
|
941
|
+
case "power": return toSurveyLogicResult(leftNumber ** rightNumber);
|
|
942
|
+
case "subtract": return toSurveyLogicResult(roundSurveyLogicSum(leftNumber, rightNumber, leftNumber - rightNumber));
|
|
943
|
+
default: return exhaustiveGuard(operator);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
// src/services/shared/compareSurveyLogicTexts.ts
|
|
947
|
+
const BASIC_PLANE_LAST_CODE_POINT = 65535;
|
|
948
|
+
const compareSurveyLogicTexts = (first, second) => {
|
|
949
|
+
let firstIndex = 0;
|
|
950
|
+
let secondIndex = 0;
|
|
951
|
+
while (firstIndex < first.length && secondIndex < second.length) {
|
|
952
|
+
const firstPoint = first.codePointAt(firstIndex) ?? 0;
|
|
953
|
+
const secondPoint = second.codePointAt(secondIndex) ?? 0;
|
|
954
|
+
if (firstPoint !== secondPoint) return firstPoint < secondPoint ? -1 : 1;
|
|
955
|
+
firstIndex += firstPoint > BASIC_PLANE_LAST_CODE_POINT ? 2 : 1;
|
|
956
|
+
secondIndex += secondPoint > BASIC_PLANE_LAST_CODE_POINT ? 2 : 1;
|
|
957
|
+
}
|
|
958
|
+
if (firstIndex < first.length) return 1;
|
|
959
|
+
return secondIndex < second.length ? -1 : 0;
|
|
960
|
+
};
|
|
961
|
+
// src/services/shared/toSurveyLogicPrimitive.ts
|
|
962
|
+
const toSurveyLogicPrimitive = (value) => Array.isArray(value) ? toSurveyLogicText(value) : value;
|
|
963
|
+
// src/services/shared/checkIsSurveyLogicLess.ts
|
|
964
|
+
const checkIsSurveyLogicLess = (left, right) => {
|
|
965
|
+
const leftPrimitive = toSurveyLogicPrimitive(left);
|
|
966
|
+
const rightPrimitive = toSurveyLogicPrimitive(right);
|
|
967
|
+
if (checkIsSurveyLogicValueEmpty(leftPrimitive) || checkIsSurveyLogicValueEmpty(rightPrimitive)) return false;
|
|
968
|
+
const leftNumber = toSurveyLogicNumber(leftPrimitive);
|
|
969
|
+
const rightNumber = toSurveyLogicNumber(rightPrimitive);
|
|
970
|
+
if (leftNumber !== void 0 && rightNumber !== void 0) return leftNumber < rightNumber;
|
|
971
|
+
if (leftNumber !== void 0 && typeof rightPrimitive === "string") return false;
|
|
972
|
+
if (rightNumber !== void 0 && typeof leftPrimitive === "string") return false;
|
|
973
|
+
if (typeof leftPrimitive === "string" && typeof rightPrimitive === "string") return compareSurveyLogicTexts(leftPrimitive, rightPrimitive) < 0;
|
|
974
|
+
return false;
|
|
975
|
+
};
|
|
976
|
+
// src/services/shared/checkIsSurveyLogicPrimitiveEqual.ts
|
|
977
|
+
const checkIsSurveyLogicPrimitiveEqual = (left, right) => {
|
|
978
|
+
if (typeof left === "boolean" || typeof right === "boolean") return left === right;
|
|
979
|
+
const leftNumber = toSurveyLogicNumber(left);
|
|
980
|
+
const rightNumber = toSurveyLogicNumber(right);
|
|
981
|
+
if (leftNumber !== void 0 || rightNumber !== void 0) return leftNumber === rightNumber;
|
|
982
|
+
return left === right;
|
|
983
|
+
};
|
|
984
|
+
// src/services/shared/checkIsSurveyLogicValueEqual.ts
|
|
985
|
+
const checkIsSurveyLogicValueEqual = (left, right) => {
|
|
986
|
+
const isLeftEmpty = checkIsSurveyLogicValueEmpty(left);
|
|
987
|
+
if (isLeftEmpty || checkIsSurveyLogicValueEmpty(right)) return isLeftEmpty && checkIsSurveyLogicValueEmpty(right);
|
|
988
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
989
|
+
if (left.length !== right.length) return false;
|
|
990
|
+
let unpairedItems = right;
|
|
991
|
+
for (const item of left) {
|
|
992
|
+
const index = unpairedItems.findIndex((rightItem) => checkIsSurveyLogicValueEqual(item, rightItem));
|
|
993
|
+
if (index === -1) return false;
|
|
994
|
+
unpairedItems = unpairedItems.toSpliced(index, 1);
|
|
995
|
+
}
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
if (Array.isArray(left) || Array.isArray(right)) return false;
|
|
999
|
+
if (typeof left === "object" && typeof right === "object") return Object.keys({
|
|
1000
|
+
...left,
|
|
1001
|
+
...right
|
|
1002
|
+
}).every((key) => checkIsSurveyLogicValueEqual(getOwnValue(left, key), getOwnValue(right, key)));
|
|
1003
|
+
if (typeof left === "object" || typeof right === "object") return false;
|
|
1004
|
+
return checkIsSurveyLogicPrimitiveEqual(left, right);
|
|
1005
|
+
};
|
|
1006
|
+
// src/services/evaluator/operators/evaluateComparisonOperator.ts
|
|
1007
|
+
const evaluateComparisonOperator = (operator, left, right) => {
|
|
1008
|
+
switch (operator) {
|
|
1009
|
+
case "equal": return checkIsSurveyLogicValueEqual(left, right);
|
|
1010
|
+
case "greater": return checkIsSurveyLogicLess(right, left);
|
|
1011
|
+
case "greaterOrEqual": return checkIsSurveyLogicValueEqual(left, right) || checkIsSurveyLogicLess(right, left);
|
|
1012
|
+
case "less": return checkIsSurveyLogicLess(left, right);
|
|
1013
|
+
case "lessOrEqual": return checkIsSurveyLogicValueEqual(left, right) || checkIsSurveyLogicLess(left, right);
|
|
1014
|
+
case "notEqual": return !checkIsSurveyLogicValueEqual(left, right);
|
|
1015
|
+
default: return exhaustiveGuard(operator);
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
// src/services/evaluator/operators/evaluateLogicOperator.ts
|
|
1019
|
+
const evaluateLogicOperator = (operator, left, right) => {
|
|
1020
|
+
switch (operator) {
|
|
1021
|
+
case "and": return left === true && right === true;
|
|
1022
|
+
case "or": return left === true || right === true;
|
|
1023
|
+
default: return exhaustiveGuard(operator);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
// src/services/shared/checkIsSurveyLogicItemHeld.ts
|
|
1027
|
+
const checkIsSurveyLogicItemHeld = (haystack, item) => {
|
|
1028
|
+
if (checkIsSurveyLogicValueEmpty(item) || checkIsSurveyLogicValueEmpty(haystack)) return false;
|
|
1029
|
+
if (Array.isArray(haystack)) return haystack.some((haystackItem) => checkIsSurveyLogicValueEqual(haystackItem, item));
|
|
1030
|
+
if (typeof haystack === "object" || typeof item === "object") return false;
|
|
1031
|
+
return toSurveyLogicText(haystack).includes(toSurveyLogicText(item));
|
|
1032
|
+
};
|
|
1033
|
+
// src/services/shared/checkIsSurveyLogicContaining.ts
|
|
1034
|
+
const checkIsSurveyLogicContaining = (left, right) => {
|
|
1035
|
+
if (checkIsSurveyLogicValueEmpty(right)) return false;
|
|
1036
|
+
return (Array.isArray(right) ? right : [right]).every((item) => checkIsSurveyLogicItemHeld(left, item));
|
|
1037
|
+
};
|
|
1038
|
+
// src/services/shared/checkIsSurveyLogicAllOf.ts
|
|
1039
|
+
const checkIsSurveyLogicAllOf = (left, right) => checkIsSurveyLogicValueEmpty(right) || checkIsSurveyLogicContaining(left, right);
|
|
1040
|
+
// src/services/shared/checkIsSurveyLogicAnyOf.ts
|
|
1041
|
+
const checkIsSurveyLogicAnyOf = (left, right) => {
|
|
1042
|
+
if (checkIsSurveyLogicValueEmpty(right)) return false;
|
|
1043
|
+
return (Array.isArray(right) ? right : [right]).some((item) => checkIsSurveyLogicItemHeld(left, item));
|
|
1044
|
+
};
|
|
1045
|
+
// src/services/evaluator/operators/evaluateMembershipOperator.ts
|
|
1046
|
+
const evaluateMembershipOperator = (operator, left, right) => {
|
|
1047
|
+
switch (operator) {
|
|
1048
|
+
case "allOf": return checkIsSurveyLogicAllOf(left, right);
|
|
1049
|
+
case "anyOf": return checkIsSurveyLogicAnyOf(left, right);
|
|
1050
|
+
case "contains": return checkIsSurveyLogicContaining(left, right);
|
|
1051
|
+
case "notContains": return !checkIsSurveyLogicContaining(left, right);
|
|
1052
|
+
default: return exhaustiveGuard(operator);
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
// src/services/evaluator/evaluateBinary.ts
|
|
1056
|
+
const evaluateBinary = (node, context, evaluate) => {
|
|
1057
|
+
const left = evaluate(node.left, context);
|
|
1058
|
+
const right = evaluate(node.right, context);
|
|
1059
|
+
switch (node.operator) {
|
|
1060
|
+
case "add":
|
|
1061
|
+
case "divide":
|
|
1062
|
+
case "modulo":
|
|
1063
|
+
case "multiply":
|
|
1064
|
+
case "power":
|
|
1065
|
+
case "subtract": return evaluateArithmeticOperator(node.operator, left, right);
|
|
1066
|
+
case "allOf":
|
|
1067
|
+
case "anyOf":
|
|
1068
|
+
case "contains":
|
|
1069
|
+
case "notContains": return evaluateMembershipOperator(node.operator, left, right);
|
|
1070
|
+
case "and":
|
|
1071
|
+
case "or": return evaluateLogicOperator(node.operator, left, right);
|
|
1072
|
+
case "equal":
|
|
1073
|
+
case "greater":
|
|
1074
|
+
case "greaterOrEqual":
|
|
1075
|
+
case "less":
|
|
1076
|
+
case "lessOrEqual":
|
|
1077
|
+
case "notEqual": return evaluateComparisonOperator(node.operator, left, right);
|
|
1078
|
+
default: return exhaustiveGuard(node.operator);
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
// src/services/evaluator/evaluateCall.ts
|
|
1082
|
+
const evaluateCall = (node, context, evaluate) => {
|
|
1083
|
+
const surveyLogicFunction = getOwnValue(SurveyLogicFunctionMap, node.name.toLowerCase());
|
|
1084
|
+
if (surveyLogicFunction === void 0) return void 0;
|
|
1085
|
+
return surveyLogicFunction(node.args.map((argument) => evaluate(argument, context)), context);
|
|
1086
|
+
};
|
|
1087
|
+
// src/services/evaluator/evaluateList.ts
|
|
1088
|
+
const evaluateList = (node, context, evaluate) => node.items.map((item) => evaluate(item, context));
|
|
1089
|
+
// src/services/evaluator/readSurveyLogicLength.ts
|
|
1090
|
+
const readSurveyLogicLength = (value) => Array.isArray(value) || typeof value === "string" ? value.length : 0;
|
|
1091
|
+
// src/services/evaluator/readSurveyLogicKey.ts
|
|
1092
|
+
const DIGITS_ONLY_REGEX = /^\d+$/u;
|
|
1093
|
+
const readSurveyLogicKey = (container, key) => {
|
|
1094
|
+
if (Array.isArray(container)) return DIGITS_ONLY_REGEX.test(key) ? container.at(Number(key)) : void 0;
|
|
1095
|
+
if (typeof container === "object") return getOwnValue(container, key);
|
|
1096
|
+
};
|
|
1097
|
+
// src/services/evaluator/readSurveyLogicPart.ts
|
|
1098
|
+
const PART_REGEX = /^(?<key>[^[]*)(?:\[(?<index>\d+)\])?$/u;
|
|
1099
|
+
const readSurveyLogicPart = (container, part) => {
|
|
1100
|
+
const match = PART_REGEX.exec(part);
|
|
1101
|
+
if (match === null) return void 0;
|
|
1102
|
+
const { index, key = "" } = match.groups ?? {};
|
|
1103
|
+
const value = readSurveyLogicKey(container, key);
|
|
1104
|
+
if (index === void 0) return value;
|
|
1105
|
+
return Array.isArray(value) ? value.at(Number(index)) : void 0;
|
|
1106
|
+
};
|
|
1107
|
+
// src/services/evaluator/readSurveyLogicPathUncached.ts
|
|
1108
|
+
const readSurveyLogicPathUncached = (container, name, readTail) => {
|
|
1109
|
+
const whole = readSurveyLogicPart(container, name);
|
|
1110
|
+
if (whole !== void 0) return whole;
|
|
1111
|
+
for (let separatorIndex = name.indexOf("."); separatorIndex !== -1; separatorIndex = name.indexOf(".", separatorIndex + 1)) {
|
|
1112
|
+
const head = readSurveyLogicPart(container, name.slice(0, separatorIndex));
|
|
1113
|
+
const tail = name.slice(separatorIndex + 1);
|
|
1114
|
+
const value = readTail(head, tail);
|
|
1115
|
+
if (value !== void 0) return value;
|
|
1116
|
+
if (tail === "length") return readSurveyLogicLength(head);
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
// src/services/evaluator/readMemoizedSurveyLogicPath.ts
|
|
1120
|
+
const readMemoizedSurveyLogicPath = (container, name, memo) => {
|
|
1121
|
+
const namesRead = getOrCreate(memo, container, () => /* @__PURE__ */ new Map());
|
|
1122
|
+
if (namesRead.has(name)) return namesRead.get(name);
|
|
1123
|
+
const value = readSurveyLogicPathUncached(container, name, (head, tail) => readMemoizedSurveyLogicPath(head, tail, memo));
|
|
1124
|
+
namesRead.set(name, value);
|
|
1125
|
+
return value;
|
|
1126
|
+
};
|
|
1127
|
+
// src/services/evaluator/readSurveyLogicPath.ts
|
|
1128
|
+
const readSurveyLogicPath = (container, name) => readMemoizedSurveyLogicPath(container, name, /* @__PURE__ */ new Map());
|
|
1129
|
+
// src/services/evaluator/readSurveyLogicReference.ts
|
|
1130
|
+
const readSurveyLogicReference = (name, context) => {
|
|
1131
|
+
const answer = getOwnValue(context.values, name);
|
|
1132
|
+
if (answer !== void 0) return answer;
|
|
1133
|
+
const separatorIndex = name.indexOf(".");
|
|
1134
|
+
const scope = separatorIndex === -1 ? void 0 : getOwnValue(context.scopes, name.slice(0, separatorIndex));
|
|
1135
|
+
if (scope !== void 0) {
|
|
1136
|
+
const scopedName = name.slice(separatorIndex + 1);
|
|
1137
|
+
return getOwnValue(scope, scopedName) ?? readSurveyLogicPath(scope, scopedName);
|
|
1138
|
+
}
|
|
1139
|
+
return readSurveyLogicPath(context.values, name);
|
|
1140
|
+
};
|
|
1141
|
+
// src/services/evaluator/evaluateReference.ts
|
|
1142
|
+
const evaluateReference = (node, context) => readSurveyLogicReference(node.name, context);
|
|
1143
|
+
// src/services/evaluator/evaluateUnary.ts
|
|
1144
|
+
const evaluateUnary = (node, context, evaluate) => {
|
|
1145
|
+
const operand = evaluate(node.operand, context);
|
|
1146
|
+
switch (node.operator) {
|
|
1147
|
+
case "empty": return checkIsSurveyLogicValueEmpty(operand);
|
|
1148
|
+
case "negate": return evaluateArithmeticOperator("subtract", 0, operand);
|
|
1149
|
+
case "not": return operand !== true;
|
|
1150
|
+
case "notEmpty": return !checkIsSurveyLogicValueEmpty(operand);
|
|
1151
|
+
default: return exhaustiveGuard(node.operator);
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
// src/services/evaluateSurveyExpression.ts
|
|
1155
|
+
const evaluateSurveyExpression = (node, context) => {
|
|
1156
|
+
switch (node.type) {
|
|
1157
|
+
case "binary": return evaluateBinary(node, context, evaluateSurveyExpression);
|
|
1158
|
+
case "call": return evaluateCall(node, context, evaluateSurveyExpression);
|
|
1159
|
+
case "list": return evaluateList(node, context, evaluateSurveyExpression);
|
|
1160
|
+
case "literal": return node.value;
|
|
1161
|
+
case "reference": return evaluateReference(node, context);
|
|
1162
|
+
case "unary": return evaluateUnary(node, context, evaluateSurveyExpression);
|
|
1163
|
+
default: return exhaustiveGuard(node);
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
// src/services/runSurveyExpression.ts
|
|
1167
|
+
const runSurveyExpression = (source, context) => parseSurveyExpression(source).map((node) => evaluateSurveyExpression(node, context));
|
|
1168
|
+
// src/services/shared/toSurveyLogicAnswer.ts
|
|
1169
|
+
const toSurveyLogicAnswers = (record) => Object.fromEntries(Object.entries(record).map(([name, answer]) => [name, toSurveyLogicAnswer(answer)]));
|
|
1170
|
+
const toSurveyLogicAnswer = (value) => {
|
|
1171
|
+
if (value === null) return void 0;
|
|
1172
|
+
if (Array.isArray(value)) return value.map((item) => toSurveyLogicAnswer(item));
|
|
1173
|
+
if (typeof value === "object") return toSurveyLogicAnswers(value);
|
|
1174
|
+
return value;
|
|
1175
|
+
};
|
|
1176
|
+
export { ArrayFunctionMap, DateFunctionMap, GeneralFunctionMap, LOWEST_POWER, MAX_DECIMAL_PLACES, MAX_SURVEY_EXPRESSION_LENGTH, PUNCTUATION_CHARACTERS, STRING_QUOTES, SYMBOL_OPERATOR_TEXTS, SurveyExpressionBinaryOperator, SurveyExpressionBinaryOperatorMap, SurveyExpressionNodeType, SurveyExpressionPostfixOperatorMap, SurveyExpressionPrefixOperatorMap, SurveyExpressionTokenType, SurveyExpressionUnaryOperator, SurveyLogicFunctionMap, addSurveyLogicMonths, advanceSurveyExpressionWhile, ageFunction, avgFunction, avgInArrayFunction, checkIsCalendarDay, checkIsPunctuation, checkIsSurveyExpressionDigit, checkIsSurveyExpressionKeyword, checkIsSurveyExpressionWhitespace, checkIsSurveyExpressionWordCharacter, checkIsSurveyExpressionWordStart, checkIsSurveyFieldName, checkIsSurveyLogicAllOf, checkIsSurveyLogicAnyOf, checkIsSurveyLogicContaining, checkIsSurveyLogicItemHeld, checkIsSurveyLogicLess, checkIsSurveyLogicPrimitiveEqual, checkIsSurveyLogicValueEmpty, checkIsSurveyLogicValueEqual, checkSurveyExpression, compareSurveyLogicTexts, countFunction, countInArrayFunction, countSurveyLogicDecimals, createSurveyLogicWallClock, dateAddFunction, dateDiffFunction, dayFunction, evaluateArithmeticOperator, evaluateBinary, evaluateCall, evaluateComparisonOperator, evaluateList, evaluateLogicOperator, evaluateMembershipOperator, evaluateReference, evaluateSurveyExpression, evaluateUnary, findSurveyExpressionRefusal, flattenSurveyNumbers, getCallRefusal, getDateDifference, getDateOrNow, getOwnValue, getSurveyArrayExtreme, getSurveyArrayItems, getSurveyArrayParams, getSurveyDateUnit, getSurveyExpressionChildren, getSurveyExtreme, getSurveyLogicDayNumber, getSurveyNumberWithPrecision, getSurveyOptionalArgument, getSurveyTextIndex, iifFunction, maxFunction, maxInArrayFunction, minFunction, minInArrayFunction, monthFunction, parseBinary, parseBinaryOperators, parseCall, parseGroup, parseList, parseOperand, parsePrefixOperator, parseSequence, parseSequenceItems, parseSurveyExpression, parseSurveyLogicDate, readMemoizedSurveyLogicPath, readSurveyExpressionNumber, readSurveyExpressionReference, readSurveyExpressionString, readSurveyExpressionSymbol, readSurveyExpressionToken, readSurveyExpressionWord, readSurveyLogicKey, readSurveyLogicLength, readSurveyLogicPart, readSurveyLogicPath, readSurveyLogicPathUncached, readSurveyLogicReference, roundFunction, roundSurveyLogicProduct, roundSurveyLogicResult, roundSurveyLogicSum, runSurveyExpression, substringFunction, sumFunction, sumInArrayFunction, sumSurveyNumbers, toSurveyLogicAnswer, toSurveyLogicAnswers, toSurveyLogicDate, toSurveyLogicNumber, toSurveyLogicPrimitive, toSurveyLogicResult, toSurveyLogicText, toSurveyLogicWallClock, todayFunction, tokenizeSurveyExpression, truncFunction, weekdayFunction, yearFunction };
|