@reidelsaltres/pureper 0.1.174 → 0.1.176
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/out/foundation/Triplet.d.ts.map +1 -1
- package/out/foundation/Triplet.js +3 -5
- package/out/foundation/Triplet.js.map +1 -1
- package/out/foundation/component_api/Component.d.ts +2 -2
- package/out/foundation/component_api/Component.d.ts.map +1 -1
- package/out/foundation/component_api/Component.js.map +1 -1
- package/out/foundation/component_api/UniHtml.d.ts +4 -4
- package/out/foundation/component_api/UniHtml.d.ts.map +1 -1
- package/out/foundation/component_api/UniHtml.js +4 -11
- package/out/foundation/component_api/UniHtml.js.map +1 -1
- package/out/foundation/engine/Expression.d.ts.map +1 -1
- package/out/foundation/engine/Expression.js.map +1 -1
- package/out/foundation/engine/TemplateEngine.d.ts +45 -3
- package/out/foundation/engine/TemplateEngine.d.ts.map +1 -1
- package/out/foundation/engine/TemplateEngine.js +385 -49
- package/out/foundation/engine/TemplateEngine.js.map +1 -1
- package/out/foundation/worker/Router.d.ts.map +1 -1
- package/out/foundation/worker/Router.js +4 -2
- package/out/foundation/worker/Router.js.map +1 -1
- package/package.json +1 -1
- package/src/foundation/Triplet.ts +5 -6
- package/src/foundation/component_api/Component.ts +2 -1
- package/src/foundation/component_api/UniHtml.ts +14 -21
- package/src/foundation/engine/Expression.ts +1 -2
- package/src/foundation/engine/TemplateEngine.ts +414 -53
- package/src/foundation/worker/Router.ts +5 -3
- package/src/foundation/engine/BalancedParser.ts +0 -353
- package/src/foundation/engine/EscapeHandler.ts +0 -54
- package/src/foundation/engine/Rule.ts +0 -138
- package/src/foundation/engine/TemplateEngine.old.ts +0 -318
- package/src/foundation/engine/TemplateInstance.md +0 -110
- package/src/foundation/engine/TemplateInstance.old.ts +0 -673
- package/src/foundation/engine/TemplateInstance.ts +0 -843
- package/src/foundation/engine/exceptions/TemplateExceptions.ts +0 -27
- package/src/foundation/engine/rules/attribute/EventRule.ts +0 -171
- package/src/foundation/engine/rules/attribute/InjectionRule.ts +0 -140
- package/src/foundation/engine/rules/attribute/RefRule.ts +0 -126
- package/src/foundation/engine/rules/syntax/ExpressionRule.ts +0 -102
- package/src/foundation/engine/rules/syntax/ForRule.ts +0 -267
- package/src/foundation/engine/rules/syntax/IfRule.ts +0 -261
- package/src/foundation/hmle/Context.ts +0 -90
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { SyntaxRule } from '../../Rule.js';
|
|
2
|
-
import type { RuleMatch, RuleResult } from '../../Rule.js';
|
|
3
|
-
import Scope from '../../Scope.js';
|
|
4
|
-
import Expression from '../../Expression.js';
|
|
5
|
-
import { InvalidTemplateEngineSyntaxException } from '../../exceptions/TemplateExceptions.js';
|
|
6
|
-
import { isObservable } from '../../../api/Observer.js';
|
|
7
|
-
|
|
8
|
-
interface ForMatch extends RuleMatch {
|
|
9
|
-
data: {
|
|
10
|
-
/** Вариант: 'single' (item in expr), 'indexed' (idx, item in expr), 'numeric' (i in number) */
|
|
11
|
-
variant: 'single' | 'indexed' | 'numeric';
|
|
12
|
-
/** Имена локальных переменных */
|
|
13
|
-
variables: string[];
|
|
14
|
-
/** Выражение коллекции/числа */
|
|
15
|
-
expression: string;
|
|
16
|
-
/** Тело цикла */
|
|
17
|
-
block: string;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* ForRule - обработка @for
|
|
23
|
-
* Варианты:
|
|
24
|
-
* 1. @for(item in collection) { ... }
|
|
25
|
-
* 2. @for(idx, item in collection) { ... }
|
|
26
|
-
* 3. @for(i in 5) { ... } - числовая итерация 0..4
|
|
27
|
-
*/
|
|
28
|
-
export default class ForRule extends SyntaxRule {
|
|
29
|
-
public readonly name = 'for';
|
|
30
|
-
public readonly priority = 10; // Выполняется раньше всех
|
|
31
|
-
|
|
32
|
-
public find(template: string): RuleMatch[] {
|
|
33
|
-
const results: RuleMatch[] = [];
|
|
34
|
-
const lowerTemplate = template.toLowerCase();
|
|
35
|
-
let i = 0;
|
|
36
|
-
|
|
37
|
-
while (i < template.length) {
|
|
38
|
-
const idx = lowerTemplate.indexOf('@for', i);
|
|
39
|
-
if (idx === -1) break;
|
|
40
|
-
|
|
41
|
-
// Check for @@ escape
|
|
42
|
-
if (idx > 0 && template[idx - 1] === '@') {
|
|
43
|
-
i = idx + 1;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const parsed = this.parseForStatement(template, idx);
|
|
48
|
-
if (parsed) {
|
|
49
|
-
results.push({
|
|
50
|
-
fullMatch: template.slice(parsed.start, parsed.end),
|
|
51
|
-
start: parsed.start,
|
|
52
|
-
end: parsed.end,
|
|
53
|
-
data: {
|
|
54
|
-
variant: parsed.variant,
|
|
55
|
-
variables: parsed.variables,
|
|
56
|
-
expression: parsed.expression,
|
|
57
|
-
block: parsed.block
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
i = parsed.end;
|
|
61
|
-
} else {
|
|
62
|
-
i = idx + 1;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return results;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private parseForStatement(template: string, start: number): {
|
|
70
|
-
start: number;
|
|
71
|
-
end: number;
|
|
72
|
-
variant: 'single' | 'indexed' | 'numeric';
|
|
73
|
-
variables: string[];
|
|
74
|
-
expression: string;
|
|
75
|
-
block: string;
|
|
76
|
-
} | null {
|
|
77
|
-
let pos = start + 4; // '@for'.length
|
|
78
|
-
|
|
79
|
-
// Skip whitespace
|
|
80
|
-
while (pos < template.length && /\s/.test(template[pos])) {
|
|
81
|
-
pos++;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Expect (
|
|
85
|
-
if (template[pos] !== '(') return null;
|
|
86
|
-
|
|
87
|
-
// Parse balanced parentheses
|
|
88
|
-
const conditionStart = pos + 1;
|
|
89
|
-
pos++;
|
|
90
|
-
let depth = 1;
|
|
91
|
-
|
|
92
|
-
while (pos < template.length && depth > 0) {
|
|
93
|
-
const ch = template[pos];
|
|
94
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
95
|
-
pos = this.skipString(template, pos, ch);
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (ch === '(') depth++;
|
|
99
|
-
else if (ch === ')') depth--;
|
|
100
|
-
pos++;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (depth !== 0) return null;
|
|
104
|
-
const conditionContent = template.slice(conditionStart, pos - 1).trim();
|
|
105
|
-
|
|
106
|
-
// Parse condition: "var in expr" or "idx, var in expr"
|
|
107
|
-
const inMatch = conditionContent.match(/^(.+?)\s+in\s+(.+)$/);
|
|
108
|
-
if (!inMatch) return null;
|
|
109
|
-
|
|
110
|
-
const varPart = inMatch[1].trim();
|
|
111
|
-
const expression = inMatch[2].trim();
|
|
112
|
-
|
|
113
|
-
// Parse variables
|
|
114
|
-
let variables: string[];
|
|
115
|
-
let variant: 'single' | 'indexed' | 'numeric';
|
|
116
|
-
|
|
117
|
-
if (varPart.includes(',')) {
|
|
118
|
-
// Indexed variant: "idx, item"
|
|
119
|
-
variables = varPart.split(',').map(v => v.trim());
|
|
120
|
-
if (variables.length !== 2) return null;
|
|
121
|
-
variant = 'indexed';
|
|
122
|
-
} else {
|
|
123
|
-
variables = [varPart];
|
|
124
|
-
variant = 'single'; // Will be determined at execution time if numeric
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Skip whitespace
|
|
128
|
-
while (pos < template.length && /\s/.test(template[pos])) {
|
|
129
|
-
pos++;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Expect {
|
|
133
|
-
if (template[pos] !== '{') return null;
|
|
134
|
-
|
|
135
|
-
// Parse balanced braces
|
|
136
|
-
const blockStart = pos + 1;
|
|
137
|
-
pos++;
|
|
138
|
-
depth = 1;
|
|
139
|
-
|
|
140
|
-
while (pos < template.length && depth > 0) {
|
|
141
|
-
const ch = template[pos];
|
|
142
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
143
|
-
pos = this.skipString(template, pos, ch);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (ch === '{') depth++;
|
|
147
|
-
else if (ch === '}') depth--;
|
|
148
|
-
pos++;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (depth !== 0) return null;
|
|
152
|
-
const block = template.slice(blockStart, pos - 1);
|
|
153
|
-
|
|
154
|
-
return { start, end: pos, variant, variables, expression, block };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private skipString(input: string, pos: number, quote: string): number {
|
|
158
|
-
pos++;
|
|
159
|
-
while (pos < input.length) {
|
|
160
|
-
if (input[pos] === '\\') {
|
|
161
|
-
pos += 2;
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
if (input[pos] === quote) {
|
|
165
|
-
return pos + 1;
|
|
166
|
-
}
|
|
167
|
-
pos++;
|
|
168
|
-
}
|
|
169
|
-
return pos;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
public execute(match: RuleMatch, scope: Scope, engine?: any): RuleResult {
|
|
173
|
-
const data = (match as ForMatch).data;
|
|
174
|
-
const observables: any[] = [];
|
|
175
|
-
const outputs: string[] = [];
|
|
176
|
-
|
|
177
|
-
// Evaluate expression
|
|
178
|
-
const expr = new Expression(data.expression);
|
|
179
|
-
|
|
180
|
-
// Находим Observable в выражении
|
|
181
|
-
const exprObservables = expr.findObservables(scope);
|
|
182
|
-
observables.push(...exprObservables);
|
|
183
|
-
|
|
184
|
-
let collection = expr.execute(scope);
|
|
185
|
-
|
|
186
|
-
// Check if Observable
|
|
187
|
-
if (isObservable(collection)) {
|
|
188
|
-
observables.push(collection);
|
|
189
|
-
collection = collection.getObject?.() ?? collection;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Determine iteration type
|
|
193
|
-
if (typeof collection === 'number') {
|
|
194
|
-
// Numeric iteration: 0 to collection-1
|
|
195
|
-
if (data.variant === 'indexed') {
|
|
196
|
-
throw new InvalidTemplateEngineSyntaxException(
|
|
197
|
-
'@for with numeric value does not support indexed variant (idx, var). Use single variable.'
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const count = Math.floor(collection);
|
|
202
|
-
if (count < 0) {
|
|
203
|
-
throw new InvalidTemplateEngineSyntaxException(
|
|
204
|
-
`@for numeric value must be non-negative, got: ${count}`
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
for (let i = 0; i < count; i++) {
|
|
209
|
-
const localScope = scope.createChild({ [data.variables[0]]: i });
|
|
210
|
-
|
|
211
|
-
if (engine) {
|
|
212
|
-
const result = engine.processTemplate(data.block, localScope);
|
|
213
|
-
outputs.push(result.output);
|
|
214
|
-
if (result.observables) observables.push(...result.observables);
|
|
215
|
-
} else {
|
|
216
|
-
outputs.push(data.block);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
} else if (Array.isArray(collection) || (collection && typeof collection[Symbol.iterator] === 'function')) {
|
|
220
|
-
// Array or iterable
|
|
221
|
-
const items = Array.isArray(collection) ? collection : Array.from(collection);
|
|
222
|
-
|
|
223
|
-
if (data.variant === 'indexed') {
|
|
224
|
-
// (idx, item in collection)
|
|
225
|
-
items.forEach((item, idx) => {
|
|
226
|
-
const localScope = scope.createChild({
|
|
227
|
-
[data.variables[0]]: idx,
|
|
228
|
-
[data.variables[1]]: item
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
if (engine) {
|
|
232
|
-
const result = engine.processTemplate(data.block, localScope);
|
|
233
|
-
outputs.push(result.output);
|
|
234
|
-
if (result.observables) observables.push(...result.observables);
|
|
235
|
-
} else {
|
|
236
|
-
outputs.push(data.block);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
} else {
|
|
240
|
-
// (item in collection)
|
|
241
|
-
items.forEach((item) => {
|
|
242
|
-
const localScope = scope.createChild({ [data.variables[0]]: item });
|
|
243
|
-
|
|
244
|
-
if (engine) {
|
|
245
|
-
const result = engine.processTemplate(data.block, localScope);
|
|
246
|
-
outputs.push(result.output);
|
|
247
|
-
if (result.observables) observables.push(...result.observables);
|
|
248
|
-
} else {
|
|
249
|
-
outputs.push(data.block);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
} else if (typeof collection === 'string') {
|
|
254
|
-
throw new InvalidTemplateEngineSyntaxException(
|
|
255
|
-
`@for does not support string iteration. Got: "${collection}"`
|
|
256
|
-
);
|
|
257
|
-
} else if (collection === null || collection === undefined) {
|
|
258
|
-
// Empty result
|
|
259
|
-
} else {
|
|
260
|
-
throw new InvalidTemplateEngineSyntaxException(
|
|
261
|
-
`@for expression must return a number, array, or iterable. Got: ${typeof collection}`
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return { output: outputs.join(''), observables };
|
|
266
|
-
}
|
|
267
|
-
}
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { SyntaxRule } from '../../Rule.js';
|
|
2
|
-
import type { RuleMatch, RuleResult } from '../../Rule.js';
|
|
3
|
-
import Scope from '../../Scope.js';
|
|
4
|
-
import Expression from '../../Expression.js';
|
|
5
|
-
import BalancedParser from '../../BalancedParser.js';
|
|
6
|
-
import { InvalidTemplateEngineSyntaxException } from '../../exceptions/TemplateExceptions.js';
|
|
7
|
-
import { isObservable } from '../../../api/Observer.js';
|
|
8
|
-
|
|
9
|
-
interface IfChainMatch extends RuleMatch {
|
|
10
|
-
data: {
|
|
11
|
-
chain: Array<{
|
|
12
|
-
type: 'if' | 'elseif' | 'else';
|
|
13
|
-
condition?: string;
|
|
14
|
-
block: string;
|
|
15
|
-
}>;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* IfRule - обработка @if/@elseif/@else
|
|
21
|
-
*/
|
|
22
|
-
export default class IfRule extends SyntaxRule {
|
|
23
|
-
public readonly name = 'if';
|
|
24
|
-
public readonly priority = 20; // Выполняется раньше @expression
|
|
25
|
-
|
|
26
|
-
public find(template: string): RuleMatch[] {
|
|
27
|
-
const results: RuleMatch[] = [];
|
|
28
|
-
const lowerTemplate = template.toLowerCase();
|
|
29
|
-
let i = 0;
|
|
30
|
-
|
|
31
|
-
while (i < template.length) {
|
|
32
|
-
const idx = lowerTemplate.indexOf('@if', i);
|
|
33
|
-
if (idx === -1) break;
|
|
34
|
-
|
|
35
|
-
// Check for @@ escape
|
|
36
|
-
if (idx > 0 && template[idx - 1] === '@') {
|
|
37
|
-
i = idx + 1;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Parse the full if/elseif/else chain
|
|
42
|
-
const chain = this.parseIfChain(template, idx);
|
|
43
|
-
if (chain) {
|
|
44
|
-
results.push({
|
|
45
|
-
fullMatch: template.slice(chain.start, chain.end),
|
|
46
|
-
start: chain.start,
|
|
47
|
-
end: chain.end,
|
|
48
|
-
data: { chain: chain.items }
|
|
49
|
-
});
|
|
50
|
-
i = chain.end;
|
|
51
|
-
} else {
|
|
52
|
-
i = idx + 1;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return results;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private parseIfChain(template: string, startIdx: number): {
|
|
60
|
-
start: number;
|
|
61
|
-
end: number;
|
|
62
|
-
items: Array<{ type: 'if' | 'elseif' | 'else'; condition?: string; block: string }>
|
|
63
|
-
} | null {
|
|
64
|
-
const items: Array<{ type: 'if' | 'elseif' | 'else'; condition?: string; block: string }> = [];
|
|
65
|
-
let pos = startIdx;
|
|
66
|
-
|
|
67
|
-
// Parse @if
|
|
68
|
-
const ifParsed = this.parseConditionBlock(template, pos, '@if');
|
|
69
|
-
if (!ifParsed) return null;
|
|
70
|
-
|
|
71
|
-
items.push({ type: 'if', condition: ifParsed.condition, block: ifParsed.block });
|
|
72
|
-
pos = ifParsed.end;
|
|
73
|
-
|
|
74
|
-
// Parse @elseif/@else chain
|
|
75
|
-
while (pos < template.length) {
|
|
76
|
-
// Skip whitespace
|
|
77
|
-
while (pos < template.length && /\s/.test(template[pos])) {
|
|
78
|
-
pos++;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const remaining = template.slice(pos).toLowerCase();
|
|
82
|
-
|
|
83
|
-
if (remaining.startsWith('@elseif')) {
|
|
84
|
-
const elseifParsed = this.parseConditionBlock(template, pos, '@elseif');
|
|
85
|
-
if (!elseifParsed) break;
|
|
86
|
-
items.push({ type: 'elseif', condition: elseifParsed.condition, block: elseifParsed.block });
|
|
87
|
-
pos = elseifParsed.end;
|
|
88
|
-
} else if (remaining.startsWith('@else') && !remaining.startsWith('@elseif')) {
|
|
89
|
-
const elseParsed = this.parseElseBlock(template, pos);
|
|
90
|
-
if (!elseParsed) break;
|
|
91
|
-
items.push({ type: 'else', block: elseParsed.block });
|
|
92
|
-
pos = elseParsed.end;
|
|
93
|
-
break; // @else is always last
|
|
94
|
-
} else {
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return { start: startIdx, end: pos, items };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private parseConditionBlock(template: string, start: number, keyword: string): {
|
|
103
|
-
condition: string;
|
|
104
|
-
block: string;
|
|
105
|
-
end: number;
|
|
106
|
-
} | null {
|
|
107
|
-
let pos = start + keyword.length;
|
|
108
|
-
|
|
109
|
-
// Skip whitespace
|
|
110
|
-
while (pos < template.length && /\s/.test(template[pos])) {
|
|
111
|
-
pos++;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Expect (
|
|
115
|
-
if (template[pos] !== '(') return null;
|
|
116
|
-
|
|
117
|
-
// Parse balanced condition
|
|
118
|
-
const conditionStart = pos + 1;
|
|
119
|
-
pos++;
|
|
120
|
-
let depth = 1;
|
|
121
|
-
|
|
122
|
-
while (pos < template.length && depth > 0) {
|
|
123
|
-
const ch = template[pos];
|
|
124
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
125
|
-
pos = this.skipString(template, pos, ch);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (ch === '(') depth++;
|
|
129
|
-
else if (ch === ')') depth--;
|
|
130
|
-
pos++;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (depth !== 0) return null;
|
|
134
|
-
const condition = template.slice(conditionStart, pos - 1);
|
|
135
|
-
|
|
136
|
-
// Skip whitespace
|
|
137
|
-
while (pos < template.length && /\s/.test(template[pos])) {
|
|
138
|
-
pos++;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Expect {
|
|
142
|
-
if (template[pos] !== '{') return null;
|
|
143
|
-
|
|
144
|
-
// Parse balanced block
|
|
145
|
-
const blockStart = pos + 1;
|
|
146
|
-
pos++;
|
|
147
|
-
depth = 1;
|
|
148
|
-
|
|
149
|
-
while (pos < template.length && depth > 0) {
|
|
150
|
-
const ch = template[pos];
|
|
151
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
152
|
-
pos = this.skipString(template, pos, ch);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (ch === '{') depth++;
|
|
156
|
-
else if (ch === '}') depth--;
|
|
157
|
-
pos++;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (depth !== 0) return null;
|
|
161
|
-
const block = template.slice(blockStart, pos - 1);
|
|
162
|
-
|
|
163
|
-
return { condition, block, end: pos };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private parseElseBlock(template: string, start: number): { block: string; end: number } | null {
|
|
167
|
-
let pos = start + 5; // '@else'.length
|
|
168
|
-
|
|
169
|
-
// Skip whitespace
|
|
170
|
-
while (pos < template.length && /\s/.test(template[pos])) {
|
|
171
|
-
pos++;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Expect {
|
|
175
|
-
if (template[pos] !== '{') return null;
|
|
176
|
-
|
|
177
|
-
// Parse balanced block
|
|
178
|
-
const blockStart = pos + 1;
|
|
179
|
-
pos++;
|
|
180
|
-
let depth = 1;
|
|
181
|
-
|
|
182
|
-
while (pos < template.length && depth > 0) {
|
|
183
|
-
const ch = template[pos];
|
|
184
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
185
|
-
pos = this.skipString(template, pos, ch);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (ch === '{') depth++;
|
|
189
|
-
else if (ch === '}') depth--;
|
|
190
|
-
pos++;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (depth !== 0) return null;
|
|
194
|
-
const block = template.slice(blockStart, pos - 1);
|
|
195
|
-
|
|
196
|
-
return { block, end: pos };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private skipString(input: string, pos: number, quote: string): number {
|
|
200
|
-
pos++;
|
|
201
|
-
while (pos < input.length) {
|
|
202
|
-
if (input[pos] === '\\') {
|
|
203
|
-
pos += 2;
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
if (input[pos] === quote) {
|
|
207
|
-
return pos + 1;
|
|
208
|
-
}
|
|
209
|
-
pos++;
|
|
210
|
-
}
|
|
211
|
-
return pos;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
public execute(match: RuleMatch, scope: Scope, engine?: any): RuleResult {
|
|
215
|
-
const chain = (match as IfChainMatch).data.chain;
|
|
216
|
-
const observables: any[] = [];
|
|
217
|
-
|
|
218
|
-
for (const item of chain) {
|
|
219
|
-
if (item.type === 'else') {
|
|
220
|
-
// Process else block
|
|
221
|
-
if (engine) {
|
|
222
|
-
const result = engine.processTemplate(item.block, scope);
|
|
223
|
-
return { output: result.output, observables: [...observables, ...(result.observables || [])] };
|
|
224
|
-
}
|
|
225
|
-
return { output: item.block, observables };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Evaluate condition
|
|
229
|
-
const expr = new Expression(item.condition!);
|
|
230
|
-
|
|
231
|
-
// Находим Observable в условии
|
|
232
|
-
const conditionObservables = expr.findObservables(scope);
|
|
233
|
-
observables.push(...conditionObservables);
|
|
234
|
-
|
|
235
|
-
const conditionResult = expr.execute(scope);
|
|
236
|
-
|
|
237
|
-
// Check if result is Observable
|
|
238
|
-
if (isObservable(conditionResult)) {
|
|
239
|
-
observables.push(conditionResult);
|
|
240
|
-
const value = conditionResult.getObject?.() ?? conditionResult;
|
|
241
|
-
if (value) {
|
|
242
|
-
if (engine) {
|
|
243
|
-
const result = engine.processTemplate(item.block, scope);
|
|
244
|
-
return { output: result.output, observables: [...observables, ...(result.observables || [])] };
|
|
245
|
-
}
|
|
246
|
-
return { output: item.block, observables };
|
|
247
|
-
}
|
|
248
|
-
} else if (conditionResult) {
|
|
249
|
-
// Condition is truthy
|
|
250
|
-
if (engine) {
|
|
251
|
-
const result = engine.processTemplate(item.block, scope);
|
|
252
|
-
return { output: result.output, observables: [...observables, ...(result.observables || [])] };
|
|
253
|
-
}
|
|
254
|
-
return { output: item.block, observables };
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// No condition matched and no else
|
|
259
|
-
return { output: '', observables };
|
|
260
|
-
}
|
|
261
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context — utility for building a safe evaluation context used by parsers
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - Merges parser variables and local scope objects
|
|
6
|
-
* - Safely copies instance prototype functions bound to the instance
|
|
7
|
-
* - Supports merging multiple contexts or objects
|
|
8
|
-
*
|
|
9
|
-
* This class is intended to replace ad-hoc `buildContext` implementations in
|
|
10
|
-
* parsers such as HMLEParser/HMLEParserReborn and PHTMLParser.
|
|
11
|
-
*/
|
|
12
|
-
export default class Context {
|
|
13
|
-
private values: Record<string, any>;
|
|
14
|
-
|
|
15
|
-
constructor(initial?: Record<string, any>) {
|
|
16
|
-
this.values = Object.assign({}, initial || {});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Create a Context and merge given scope into it */
|
|
20
|
-
public static from(scopeVars?: Record<string, any>, scope?: Record<string, any>): Context {
|
|
21
|
-
const ctx = new Context(scopeVars);
|
|
22
|
-
if (scope) ctx.merge(scope);
|
|
23
|
-
return ctx;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Merge plain object or another Context into this one */
|
|
27
|
-
public merge(other?: Record<string, any> | Context): this {
|
|
28
|
-
if (!other) return this;
|
|
29
|
-
if (other instanceof Context) Object.assign(this.values, other.values);
|
|
30
|
-
else Object.assign(this.values, other);
|
|
31
|
-
return this;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Copy prototype methods from an instance into the context
|
|
36
|
-
* Methods are bound to the given instance to preserve `this` semantics.
|
|
37
|
-
*/
|
|
38
|
-
public bindPrototypeMethods(instance?: Record<string, any>): this {
|
|
39
|
-
if (!instance) return this;
|
|
40
|
-
|
|
41
|
-
const bindTarget: any = (instance as any).__hmle_this ?? instance;
|
|
42
|
-
|
|
43
|
-
let proto: any = Object.getPrototypeOf(bindTarget);
|
|
44
|
-
while (proto && proto !== Object.prototype) {
|
|
45
|
-
// avoid copying host DOM/window prototype methods
|
|
46
|
-
const ctorName = proto && proto.constructor ? String((proto as any).constructor?.name ?? '') : '';
|
|
47
|
-
if (/HTMLElement|Element|Node|EventTarget|Window|GlobalThis/i.test(ctorName)) {
|
|
48
|
-
proto = Object.getPrototypeOf(proto);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
53
|
-
if (key === 'constructor') continue;
|
|
54
|
-
if (key in this.values) continue; // do not override existing keys
|
|
55
|
-
|
|
56
|
-
let desc: PropertyDescriptor | undefined;
|
|
57
|
-
try {
|
|
58
|
-
desc = Object.getOwnPropertyDescriptor(proto, key) as PropertyDescriptor | undefined;
|
|
59
|
-
} catch (e) {
|
|
60
|
-
// Some host objects may throw when attempting to read descriptors — skip
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (!desc) continue;
|
|
64
|
-
if (typeof desc.value === 'function') {
|
|
65
|
-
try {
|
|
66
|
-
this.values[key] = (desc.value as Function).bind(bindTarget);
|
|
67
|
-
} catch (_e) {
|
|
68
|
-
// binding may fail for native host methods — skip
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
proto = Object.getPrototypeOf(proto);
|
|
75
|
-
}
|
|
76
|
-
return this;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Return a plain object suitable for `with(this)` evaluation contexts */
|
|
80
|
-
public toObject(): Record<string, any> {
|
|
81
|
-
return Object.assign({}, this.values);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Convenience static builder returning a plain object for use in parsers */
|
|
85
|
-
public static build(variables?: Record<string, any>, scope?: Record<string, any>): Record<string, any> {
|
|
86
|
-
const ctx = new Context(variables);
|
|
87
|
-
if (scope) ctx.merge(scope).bindPrototypeMethods(scope);
|
|
88
|
-
return ctx.toObject();
|
|
89
|
-
}
|
|
90
|
-
}
|