@oh-my-pi/omptype 18.16.0 → 18.18.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.
@@ -0,0 +1,514 @@
1
+ /**
2
+ * JIT backend: generates a specialized boolean validator for a {@link Plan}
3
+ * graph via `new Function`.
4
+ *
5
+ * Plans inline into the function that reaches them; `$ref` targets, recursive
6
+ * plans, and repeated non-leaf subtrees become their own functions. Failures
7
+ * jump with `return false` or a labeled `break`, so union branches, `not`, and
8
+ * `if` run inline without allocating closures. Unions whose branches share a
9
+ * required tag property compile to a `switch` on that tag. Plans that need
10
+ * annotation tracking (`unevaluated*`) delegate to the interpreter.
11
+ */
12
+ import { findDiscriminator, isRefOnly, unwrapRef, validate } from "./interpret.js";
13
+ import { kindOf, T_ANY, T_ARRAY, T_BOOLEAN, T_INTEGER, T_NULL, T_NUMBER, T_NUMERIC, T_OBJECT, T_STRING, } from "./plan.js";
14
+ import { compileSimplePattern } from "./pattern.js";
15
+ import { codePointLength, isInheritedName, isMultipleOf, jsonEqual, jsonIncludes, uniqueItems } from "./runtime.js";
16
+ const HELPERS = {
17
+ eq: jsonEqual,
18
+ inc: jsonIncludes,
19
+ uniq: uniqueItems,
20
+ mult: isMultipleOf,
21
+ cpl: codePointLength,
22
+ own: Object.hasOwn,
23
+ };
24
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
25
+ /** Set once code generation from strings is refused (strict CSP); never retried. */
26
+ let codegenBlocked = false;
27
+ /**
28
+ * Generate a validator for `root`, or `undefined` when the runtime forbids
29
+ * code generation from strings.
30
+ */
31
+ export function generate(root) {
32
+ if (codegenBlocked)
33
+ return undefined;
34
+ const source = new Generator().program(root);
35
+ let factory;
36
+ try {
37
+ factory = new Function("R", "H", source.code);
38
+ }
39
+ catch (error) {
40
+ if (!(error instanceof EvalError))
41
+ throw error;
42
+ codegenBlocked = true;
43
+ return undefined;
44
+ }
45
+ return factory(source.refs, HELPERS);
46
+ }
47
+ function literal(value) {
48
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
49
+ }
50
+ function isPrimitive(value) {
51
+ return value === null || (typeof value !== "object" && typeof value !== "function");
52
+ }
53
+ /** Boolean expression: `x` matches `types`. */
54
+ function typeTest(types, x) {
55
+ const tests = [];
56
+ if (types & T_OBJECT)
57
+ tests.push(`(typeof ${x}==="object"&&${x}!==null&&!Array.isArray(${x}))`);
58
+ if (types & T_ARRAY)
59
+ tests.push(`Array.isArray(${x})`);
60
+ if (types & T_STRING)
61
+ tests.push(`typeof ${x}==="string"`);
62
+ if (types & T_NUMBER)
63
+ tests.push(`Number.isFinite(${x})`);
64
+ else if (types & T_INTEGER)
65
+ tests.push(`Number.isInteger(${x})`);
66
+ if (types & T_BOOLEAN)
67
+ tests.push(`typeof ${x}==="boolean"`);
68
+ if (types & T_NULL)
69
+ tests.push(`${x}===null`);
70
+ return tests.length === 1 ? tests[0] : `(${tests.join("||")})`;
71
+ }
72
+ /** Boolean expression: `x` does not match `types` (single kinds avoid a negated group). */
73
+ function typeMismatch(types, x) {
74
+ switch (types) {
75
+ case T_OBJECT:
76
+ return `typeof ${x}!=="object"||${x}===null||Array.isArray(${x})`;
77
+ case T_ARRAY:
78
+ return `!Array.isArray(${x})`;
79
+ case T_STRING:
80
+ return `typeof ${x}!=="string"`;
81
+ case T_NUMERIC:
82
+ return `!Number.isFinite(${x})`;
83
+ case T_INTEGER:
84
+ return `!Number.isInteger(${x})`;
85
+ case T_BOOLEAN:
86
+ return `typeof ${x}!=="boolean"`;
87
+ case T_NULL:
88
+ return `${x}!==null`;
89
+ default:
90
+ return `!${typeTest(types, x)}`;
91
+ }
92
+ }
93
+ /** Leaf plans have no subschemas, so inlining them repeatedly stays small. */
94
+ function isLeaf(plan) {
95
+ return (plan.refs === undefined &&
96
+ plan.prefixItems === undefined &&
97
+ plan.items === undefined &&
98
+ plan.contains === undefined &&
99
+ plan.properties === undefined &&
100
+ plan.patternProperties === undefined &&
101
+ plan.additionalProperties === undefined &&
102
+ plan.propertyNames === undefined &&
103
+ plan.dependentSchemas === undefined &&
104
+ plan.allOf === undefined &&
105
+ plan.anyOf === undefined &&
106
+ plan.oneOf === undefined &&
107
+ plan.not === undefined &&
108
+ plan.if === undefined);
109
+ }
110
+ class Generator {
111
+ #refs = [];
112
+ #refIndex = new Map();
113
+ #functions = new Map();
114
+ #queue = [];
115
+ #inlined = new Set();
116
+ #inlining = new Set();
117
+ /** Pattern source → name of its char-code function or `RegExp` ref. */
118
+ #patterns = new Map();
119
+ #helpers = [];
120
+ #counter = 0;
121
+ program(root) {
122
+ const entry = this.#fn(root);
123
+ const functions = [];
124
+ for (let index = 0; index < this.#queue.length; index++) {
125
+ const plan = this.#queue[index];
126
+ this.#inlining.add(plan);
127
+ const body = this.#emitInline(plan, "x", "return false", T_ANY);
128
+ this.#inlining.delete(plan);
129
+ functions.push(`function ${this.#functions.get(plan)}(x){${body}return true}`);
130
+ }
131
+ let prelude = "const{eq,inc,uniq,mult,cpl,own}=H;";
132
+ for (let index = 0; index < this.#refs.length; index++)
133
+ prelude += `const r${index}=R[${index}];`;
134
+ return { code: `${prelude}${this.#helpers.join("")}${functions.join("")}return ${entry};`, refs: this.#refs };
135
+ }
136
+ #name(prefix) {
137
+ return `${prefix}${this.#counter++}`;
138
+ }
139
+ #ref(value) {
140
+ let index = this.#refIndex.get(value);
141
+ if (index === undefined) {
142
+ index = this.#refs.length;
143
+ this.#refs.push(value);
144
+ this.#refIndex.set(value, index);
145
+ }
146
+ return `r${index}`;
147
+ }
148
+ #fn(plan) {
149
+ let name = this.#functions.get(plan);
150
+ if (name === undefined) {
151
+ name = `f${this.#functions.size}`;
152
+ this.#functions.set(plan, name);
153
+ this.#queue.push(plan);
154
+ }
155
+ return name;
156
+ }
157
+ /** Code that runs `fail` unless `x` satisfies `plan`; `known` is a type mask `x` already satisfies. */
158
+ #emit(plan, x, fail, known) {
159
+ if (plan.types === 0)
160
+ return `${fail};`;
161
+ if (plan.empty)
162
+ return "";
163
+ if (this.#inlining.has(plan) || (this.#inlined.has(plan) && !isLeaf(plan))) {
164
+ return `if(!${this.#fn(plan)}(${x}))${fail};`;
165
+ }
166
+ this.#inlined.add(plan);
167
+ this.#inlining.add(plan);
168
+ const code = this.#emitInline(plan, x, fail, known);
169
+ this.#inlining.delete(plan);
170
+ return code;
171
+ }
172
+ #emitInline(plan, x, fail, known) {
173
+ if (plan.types === 0)
174
+ return `${fail};`;
175
+ if (plan.empty)
176
+ return "";
177
+ if (plan.unevaluatedProperties !== undefined || plan.unevaluatedItems !== undefined) {
178
+ const check = (value) => validate(plan, value);
179
+ return `if(!${this.#ref(check)}(${x}))${fail};`;
180
+ }
181
+ if (isRefOnly(plan))
182
+ return this.#emitRefs(plan.refs, x, fail);
183
+ let out = "";
184
+ let kind = known;
185
+ const types = plan.types;
186
+ if (types !== T_ANY && (known === 0 || (types & known) !== known)) {
187
+ out += `if(${typeMismatch(types, x)})${fail};`;
188
+ kind = kindOf(types);
189
+ }
190
+ if (plan.hasConst) {
191
+ out += isPrimitive(plan.constValue)
192
+ ? `if(${x}!==${literal(plan.constValue)})${fail};`
193
+ : `if(!eq(${x},${this.#ref(plan.constValue)}))${fail};`;
194
+ }
195
+ if (plan.enumValues !== undefined)
196
+ out += this.#emitEnum(plan.enumValues, x, fail);
197
+ out += this.#emitNumber(plan, x, fail, kind);
198
+ out += this.#emitString(plan, x, fail, kind);
199
+ out += this.#emitArray(plan, x, fail, kind);
200
+ out += this.#emitObject(plan, x, fail, kind);
201
+ if (plan.refs !== undefined)
202
+ out += this.#emitRefs(plan.refs, x, fail);
203
+ if (plan.allOf !== undefined)
204
+ for (const branch of plan.allOf)
205
+ out += this.#emit(branch, x, fail, kind);
206
+ if (plan.anyOf !== undefined)
207
+ out += this.#emitUnion(plan.anyOf, x, fail, kind, false);
208
+ if (plan.oneOf !== undefined)
209
+ out += this.#emitUnion(plan.oneOf, x, fail, kind, true);
210
+ if (plan.not !== undefined) {
211
+ const label = this.#name("L");
212
+ out += `${label}:{${this.#emit(plan.not, x, `break ${label}`, kind)}${fail};}`;
213
+ }
214
+ if (plan.if !== undefined && (plan.ifThen !== undefined || plan.ifElse !== undefined)) {
215
+ const done = this.#name("L");
216
+ const otherwise = this.#name("L");
217
+ const then = plan.ifThen === undefined ? "" : this.#emit(plan.ifThen, x, fail, kind);
218
+ const elseCode = plan.ifElse === undefined ? "" : this.#emit(plan.ifElse, x, fail, kind);
219
+ out += `${done}:{${otherwise}:{${this.#emit(plan.if, x, `break ${otherwise}`, kind)}${then}break ${done};}${elseCode}}`;
220
+ }
221
+ return out;
222
+ }
223
+ #emitRefs(refs, x, fail) {
224
+ let out = "";
225
+ for (const ref of refs) {
226
+ const target = unwrapRef(ref);
227
+ if (target.types === 0)
228
+ return `${fail};`;
229
+ if (target.empty)
230
+ continue;
231
+ out += `if(!${this.#fn(target)}(${x}))${fail};`;
232
+ }
233
+ return out;
234
+ }
235
+ #emitEnum(values, x, fail) {
236
+ if (!values.every(isPrimitive))
237
+ return `if(!inc(${this.#ref(values)},${x}))${fail};`;
238
+ if (values.length === 0)
239
+ return `${fail};`;
240
+ if (values.length <= 8)
241
+ return `if(${values.map(value => `${x}!==${literal(value)}`).join("&&")})${fail};`;
242
+ return `if(!${this.#ref(new Set(values))}.has(${x}))${fail};`;
243
+ }
244
+ #emitNumber(plan, x, fail, kind) {
245
+ let out = "";
246
+ if (plan.minimum !== undefined)
247
+ out += `if(${x}<${plan.minimum})${fail};`;
248
+ if (plan.exclusiveMinimum !== undefined)
249
+ out += `if(${x}<=${plan.exclusiveMinimum})${fail};`;
250
+ if (plan.maximum !== undefined)
251
+ out += `if(${x}>${plan.maximum})${fail};`;
252
+ if (plan.exclusiveMaximum !== undefined)
253
+ out += `if(${x}>=${plan.exclusiveMaximum})${fail};`;
254
+ if (plan.multipleOf !== undefined) {
255
+ out += Number.isInteger(plan.multipleOf)
256
+ ? `if(${x}%${plan.multipleOf}!==0)${fail};`
257
+ : `if(!mult(${x},${plan.multipleOf}))${fail};`;
258
+ }
259
+ if (out === "" || (kind !== 0 && (kind & T_NUMERIC) === kind))
260
+ return out;
261
+ return `if(typeof ${x}==="number"){${out}}`;
262
+ }
263
+ #emitString(plan, x, fail, kind) {
264
+ let out = "";
265
+ const { minLength, maxLength } = plan;
266
+ if (minLength !== undefined && minLength > 0) {
267
+ out +=
268
+ minLength === 1
269
+ ? `if(${x}.length<1)${fail};`
270
+ : `if(${x}.length<${minLength * 2}&&cpl(${x})<${minLength})${fail};`;
271
+ }
272
+ if (maxLength !== undefined)
273
+ out += `if(${x}.length>${maxLength}&&cpl(${x})>${maxLength})${fail};`;
274
+ if (plan.pattern !== undefined)
275
+ out += `if(!${this.#pattern(plan.pattern)}(${x}))${fail};`;
276
+ if (plan.format !== undefined)
277
+ out += `if(!${this.#ref(plan.format)}(${x}))${fail};`;
278
+ if (out === "" || kind === T_STRING)
279
+ return out;
280
+ return `if(typeof ${x}==="string"){${out}}`;
281
+ }
282
+ /** Callee testing a string against `pattern`: a char-code function when simple, else the regex's `test`. */
283
+ #pattern(pattern) {
284
+ let callee = this.#patterns.get(pattern.source);
285
+ if (callee === undefined) {
286
+ const name = this.#name("pat");
287
+ const helper = compileSimplePattern(pattern.source, name);
288
+ if (helper === undefined) {
289
+ callee = `${this.#ref(pattern)}.test`;
290
+ }
291
+ else {
292
+ this.#helpers.push(helper);
293
+ callee = name;
294
+ }
295
+ this.#patterns.set(pattern.source, callee);
296
+ }
297
+ return callee;
298
+ }
299
+ #emitArray(plan, x, fail, kind) {
300
+ const n = this.#name("n");
301
+ let out = "";
302
+ if (plan.minItems !== undefined && plan.minItems > 0)
303
+ out += `if(${n}<${plan.minItems})${fail};`;
304
+ if (plan.maxItems !== undefined)
305
+ out += `if(${n}>${plan.maxItems})${fail};`;
306
+ const prefix = plan.prefixItems ?? [];
307
+ for (let index = 0; index < prefix.length; index++) {
308
+ const item = prefix[index];
309
+ if (item.empty)
310
+ continue;
311
+ const element = this.#name("e");
312
+ out += `if(${n}>${index}){const ${element}=${x}[${index}];${this.#emit(item, element, fail, 0)}}`;
313
+ }
314
+ if (plan.items !== undefined && !plan.items.empty) {
315
+ if (plan.items.types === 0) {
316
+ out += `if(${n}>${prefix.length})${fail};`;
317
+ }
318
+ else {
319
+ const index = this.#name("i");
320
+ const element = this.#name("e");
321
+ out += `for(let ${index}=${prefix.length};${index}<${n};${index}++){const ${element}=${x}[${index}];${this.#emit(plan.items, element, fail, 0)}}`;
322
+ }
323
+ }
324
+ if (plan.contains !== undefined) {
325
+ const atLeast = plan.minContains ?? 1;
326
+ const atMost = plan.maxContains;
327
+ if (atLeast > 0 || atMost !== undefined) {
328
+ const count = this.#name("c");
329
+ const index = this.#name("i");
330
+ const label = this.#name("L");
331
+ const element = `${x}[${index}]`;
332
+ const early = atMost === undefined ? `if(${count}>=${atLeast})break;` : "";
333
+ out += `let ${count}=0;for(let ${index}=0;${index}<${n};${index}++){${label}:{${this.#emit(plan.contains, element, `break ${label}`, 0)}${count}++;${early}}}`;
334
+ if (atLeast > 0)
335
+ out += `if(${count}<${atLeast})${fail};`;
336
+ if (atMost !== undefined)
337
+ out += `if(${count}>${atMost})${fail};`;
338
+ }
339
+ }
340
+ if (plan.uniqueItems)
341
+ out += `if(!uniq(${x}))${fail};`;
342
+ if (out === "")
343
+ return out;
344
+ out = `const ${n}=${x}.length;${out}`;
345
+ return kind === T_ARRAY ? out : `if(Array.isArray(${x})){${out}}`;
346
+ }
347
+ #get(x, key) {
348
+ const access = IDENTIFIER.test(key) ? `${x}.${key}` : `${x}[${JSON.stringify(key)}]`;
349
+ return isInheritedName(key) ? `(own(${x},${JSON.stringify(key)})?${access}:undefined)` : access;
350
+ }
351
+ #emitObject(plan, x, fail, kind) {
352
+ let out = "";
353
+ const required = new Set(plan.required);
354
+ const properties = plan.properties ?? [];
355
+ const declared = new Set(properties.map(property => property.key));
356
+ for (const key of required) {
357
+ if (!declared.has(key))
358
+ out += `if(${this.#get(x, key)}===undefined)${fail};`;
359
+ }
360
+ // Closed objects whose required keys are all declared: when the key count equals the number of
361
+ // declared keys present, no other key exists; otherwise a slow pass finds the undeclared one.
362
+ const additional = plan.additionalProperties;
363
+ const counted = additional !== undefined &&
364
+ additional.types === 0 &&
365
+ plan.patternProperties === undefined &&
366
+ (plan.propertyNames === undefined || plan.propertyNames.empty) &&
367
+ plan.minProperties === undefined &&
368
+ plan.maxProperties === undefined &&
369
+ [...required].every(key => declared.has(key));
370
+ const present = this.#name("pc");
371
+ let optional = false;
372
+ for (const property of properties) {
373
+ const isRequired = required.has(property.key);
374
+ if (property.plan.empty) {
375
+ if (isRequired)
376
+ out += `if(${this.#get(x, property.key)}===undefined)${fail};`;
377
+ else if (counted) {
378
+ out += `if(${this.#get(x, property.key)}!==undefined)${present}++;`;
379
+ optional = true;
380
+ }
381
+ continue;
382
+ }
383
+ const value = this.#name("p");
384
+ const check = this.#emit(property.plan, value, fail, 0);
385
+ out += `const ${value}=${this.#get(x, property.key)};`;
386
+ if (isRequired)
387
+ out += `if(${value}===undefined)${fail};${check}`;
388
+ else {
389
+ out += `if(${value}!==undefined){${counted ? `${present}++;` : ""}${check}}`;
390
+ optional ||= counted;
391
+ }
392
+ }
393
+ if (counted) {
394
+ const key = this.#name("k");
395
+ const count = this.#name("c");
396
+ const expected = optional ? present : String(required.size);
397
+ if (optional)
398
+ out = `let ${present}=${required.size};${out}`;
399
+ out += `let ${count}=0;for(const ${key} in ${x})${count}++;`;
400
+ out += `if(${count}!==${expected}){for(const ${key} in ${x})if(!${this.#isDeclared(key, declared)})${fail};}`;
401
+ }
402
+ else {
403
+ out += this.#emitKeys(plan, x, fail, declared);
404
+ }
405
+ if (plan.dependentRequired !== undefined) {
406
+ for (const dependency of plan.dependentRequired) {
407
+ const missing = dependency.required.map(key => `${this.#get(x, key)}===undefined`).join("||");
408
+ out += `if(${this.#get(x, dependency.key)}!==undefined&&(${missing}))${fail};`;
409
+ }
410
+ }
411
+ if (plan.dependentSchemas !== undefined) {
412
+ for (const dependency of plan.dependentSchemas) {
413
+ out += `if(${this.#get(x, dependency.key)}!==undefined){${this.#emit(dependency.plan, x, fail, T_OBJECT)}}`;
414
+ }
415
+ }
416
+ if (out === "" || kind === T_OBJECT)
417
+ return out;
418
+ return `if(typeof ${x}==="object"&&${x}!==null&&!Array.isArray(${x})){${out}}`;
419
+ }
420
+ /** The `for…in` pass: `patternProperties`, `additionalProperties`, `propertyNames`, property counts. */
421
+ #emitKeys(plan, x, fail, declared) {
422
+ const patterns = plan.patternProperties;
423
+ const additional = plan.additionalProperties;
424
+ const names = plan.propertyNames;
425
+ const { minProperties, maxProperties } = plan;
426
+ const counting = minProperties !== undefined || maxProperties !== undefined;
427
+ const checkAdditional = additional !== undefined && !additional.empty;
428
+ const checkNames = names !== undefined && !names.empty;
429
+ if (patterns === undefined && !checkAdditional && !checkNames && !counting)
430
+ return "";
431
+ const key = this.#name("k");
432
+ const count = this.#name("c");
433
+ let body = "";
434
+ if (counting)
435
+ body += `${count}++;`;
436
+ if (checkNames)
437
+ body += this.#emit(names, key, fail, T_STRING);
438
+ if (patterns !== undefined || checkAdditional) {
439
+ const matched = this.#name("m");
440
+ const tracking = checkAdditional && (patterns !== undefined || declared.size > 0);
441
+ if (tracking)
442
+ body += `let ${matched}=${this.#isDeclared(key, declared)};`;
443
+ for (const { pattern, plan: patternPlan } of patterns ?? []) {
444
+ const value = this.#name("v");
445
+ const check = patternPlan.empty
446
+ ? ""
447
+ : `const ${value}=${x}[${key}];${this.#emit(patternPlan, value, fail, 0)}`;
448
+ const mark = tracking ? `${matched}=true;` : "";
449
+ if (check !== "" || mark !== "")
450
+ body += `if(${this.#ref(pattern)}.test(${key})){${mark}${check}}`;
451
+ }
452
+ if (checkAdditional) {
453
+ const value = this.#name("v");
454
+ const check = additional.types === 0
455
+ ? `${fail};`
456
+ : `const ${value}=${x}[${key}];${this.#emit(additional, value, fail, 0)}`;
457
+ body += tracking ? `if(!${matched}){${check}}` : `{${check}}`;
458
+ }
459
+ }
460
+ let out = `for(const ${key} in ${x}){${body}}`;
461
+ if (counting) {
462
+ out = `let ${count}=0;${out}`;
463
+ if (minProperties !== undefined)
464
+ out += `if(${count}<${minProperties})${fail};`;
465
+ if (maxProperties !== undefined)
466
+ out += `if(${count}>${maxProperties})${fail};`;
467
+ }
468
+ return out;
469
+ }
470
+ #isDeclared(key, declared) {
471
+ if (declared.size === 0)
472
+ return "false";
473
+ if (declared.size > 8)
474
+ return `${this.#ref(declared)}.has(${key})`;
475
+ return `(${[...declared].map(name => `${key}===${JSON.stringify(name)}`).join("||")})`;
476
+ }
477
+ #emitUnion(branches, x, fail, kind, exclusive) {
478
+ const discriminator = findDiscriminator(branches);
479
+ if (discriminator !== undefined) {
480
+ const byBranch = new Map();
481
+ for (const [tag, index] of discriminator.branches) {
482
+ const tags = byBranch.get(index);
483
+ if (tags === undefined)
484
+ byBranch.set(index, [tag]);
485
+ else
486
+ tags.push(tag);
487
+ }
488
+ let cases = "";
489
+ for (const [index, tags] of byBranch) {
490
+ cases += `${tags.map(tag => `case ${literal(tag)}:`).join("")}{${this.#emit(branches[index], x, fail, T_OBJECT)}break;}`;
491
+ }
492
+ const guard = kind === T_OBJECT ? "" : `if(${typeMismatch(T_OBJECT, x)})${fail};`;
493
+ return `${guard}switch(${this.#get(x, discriminator.key)}){${cases}default:${fail};}`;
494
+ }
495
+ if (!exclusive && branches.some(branch => branch.empty))
496
+ return "";
497
+ if (exclusive) {
498
+ const count = this.#name("c");
499
+ let out = `let ${count}=0;`;
500
+ for (const branch of branches) {
501
+ const label = this.#name("L");
502
+ out += `${label}:{${this.#emit(branch, x, `break ${label}`, kind)}if(++${count}>1)${fail};}`;
503
+ }
504
+ return `${out}if(${count}===0)${fail};`;
505
+ }
506
+ const done = this.#name("L");
507
+ let out = `${done}:{`;
508
+ for (const branch of branches) {
509
+ const label = this.#name("L");
510
+ out += `${label}:{${this.#emit(branch, x, `break ${label}`, kind)}break ${done};}`;
511
+ }
512
+ return `${out}${fail};}`;
513
+ }
514
+ }