@gtkx/runtime 1.2.2 → 1.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/README.md +6 -4
- package/dist/arg.d.ts +6 -1
- package/dist/arg.d.ts.map +1 -1
- package/dist/arg.js +2 -1
- package/dist/arg.js.map +1 -1
- package/dist/closure.d.ts.map +1 -1
- package/dist/closure.js +2 -11
- package/dist/closure.js.map +1 -1
- package/dist/descriptors.d.ts +21 -3
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +50 -21
- package/dist/descriptors.js.map +1 -1
- package/dist/field.d.ts +42 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +41 -0
- package/dist/field.js.map +1 -0
- package/dist/fn.d.ts +2 -0
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +50 -10
- package/dist/fn.js.map +1 -1
- package/dist/index.d.ts +14 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/mixin.d.ts +2 -1
- package/dist/mixin.d.ts.map +1 -1
- package/dist/mixin.js +1 -1
- package/dist/mixin.js.map +1 -1
- package/dist/native-value.d.ts +9 -3
- package/dist/native-value.d.ts.map +1 -1
- package/dist/native-value.js +86 -9
- package/dist/native-value.js.map +1 -1
- package/dist/object.d.ts +14 -6
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +21 -10
- package/dist/object.js.map +1 -1
- package/dist/param-spec.d.ts +32 -1
- package/dist/param-spec.d.ts.map +1 -1
- package/dist/param-spec.js +67 -5
- package/dist/param-spec.js.map +1 -1
- package/dist/promisify.d.ts +13 -1
- package/dist/promisify.d.ts.map +1 -1
- package/dist/promisify.js +14 -1
- package/dist/promisify.js.map +1 -1
- package/dist/properties.d.ts +48 -5
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +191 -20
- package/dist/properties.js.map +1 -1
- package/dist/regex.d.ts +25 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +59 -0
- package/dist/regex.js.map +1 -0
- package/dist/register-class.d.ts +165 -15
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +175 -19
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +48 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +147 -3
- package/dist/registry.js.map +1 -1
- package/dist/signal.d.ts +11 -1
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +66 -6
- package/dist/signal.js.map +1 -1
- package/dist/t.d.ts +11 -0
- package/dist/t.d.ts.map +1 -1
- package/dist/t.js +3 -0
- package/dist/t.js.map +1 -1
- package/dist/type.d.ts +2 -1
- package/dist/type.d.ts.map +1 -1
- package/dist/type.js +5 -1
- package/dist/type.js.map +1 -1
- package/dist/value.d.ts +48 -4
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +172 -16
- package/dist/value.js.map +1 -1
- package/dist/variant.d.ts +75 -26
- package/dist/variant.d.ts.map +1 -1
- package/dist/variant.js +44 -29
- package/dist/variant.js.map +1 -1
- package/package.json +5 -5
- package/src/arg.ts +6 -1
- package/src/closure.ts +2 -14
- package/src/descriptors.ts +74 -21
- package/src/field.ts +76 -0
- package/src/fn.ts +73 -17
- package/src/index.ts +46 -5
- package/src/mixin.ts +1 -1
- package/src/native-value.ts +146 -14
- package/src/object.ts +23 -11
- package/src/param-spec.ts +96 -8
- package/src/promisify.ts +27 -1
- package/src/properties.ts +280 -32
- package/src/regex.ts +92 -0
- package/src/register-class.ts +485 -42
- package/src/registry.ts +219 -4
- package/src/signal.ts +117 -4
- package/src/t.ts +13 -0
- package/src/type.ts +7 -0
- package/src/value.ts +268 -14
- package/src/variant.ts +184 -53
package/src/properties.ts
CHANGED
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
import type { ExternalObject, Handle, RegisterClassProperty } from "@gtkx/native";
|
|
2
2
|
import type { AnyClass } from "@gtkx/utils";
|
|
3
|
-
import { camelCase, kebabCase } from "@gtkx/utils";
|
|
3
|
+
import { camelCase, kebabCase, toCamelIdentifier } from "@gtkx/utils";
|
|
4
4
|
import { bind } from "./bind.js";
|
|
5
|
-
import { biguint64T, stringT, structT, voidT } from "./descriptors.js";
|
|
5
|
+
import { biguint64T, fundamentalT, refT, sizedArrayT, stringT, structT, uint32T, voidT } from "./descriptors.js";
|
|
6
6
|
import { LIB, PARAM_T, VALUE_T } from "./library.js";
|
|
7
7
|
import {
|
|
8
8
|
getParamFlags,
|
|
9
9
|
getParamValueType,
|
|
10
10
|
isParamConstructOnly,
|
|
11
|
+
isParamExplicitlyNotified,
|
|
11
12
|
isParamLaxlyValidated,
|
|
13
|
+
isParamReadable,
|
|
12
14
|
isParamWritable,
|
|
13
15
|
type ValueGuard,
|
|
14
16
|
valueGuardFor,
|
|
15
17
|
wasParamValueModified,
|
|
18
|
+
WHOLE_NUMBER_RANGES,
|
|
16
19
|
} from "./param-spec.js";
|
|
17
|
-
import {
|
|
18
|
-
|
|
20
|
+
import {
|
|
21
|
+
getClassType,
|
|
22
|
+
getHandle,
|
|
23
|
+
getInstanceType,
|
|
24
|
+
getInterfaceProperties,
|
|
25
|
+
instanceClassName,
|
|
26
|
+
type InterfaceProperty,
|
|
27
|
+
} from "./registry.js";
|
|
28
|
+
import { TYPE_INTERFACE, TYPE_INVALID, TYPE_OBJECT, typeFundamental, typeIsA, typeName } from "./type.js";
|
|
19
29
|
import {
|
|
20
30
|
fromValue,
|
|
21
31
|
newValueForType,
|
|
32
|
+
valueGuardOverrideFor,
|
|
22
33
|
type ValueNarrower,
|
|
23
34
|
valueNarrowerFor,
|
|
24
35
|
type ValueWriter,
|
|
@@ -37,14 +48,19 @@ type PropertyCheck = {
|
|
|
37
48
|
scratch?: ExternalObject<Handle>;
|
|
38
49
|
};
|
|
39
50
|
|
|
40
|
-
type
|
|
41
|
-
memberName: string;
|
|
51
|
+
type AccessorBase = PropertyCheck & {
|
|
42
52
|
storage: symbol;
|
|
43
|
-
hasGeneratedMember: boolean;
|
|
44
|
-
isInterfaceProperty?: boolean;
|
|
45
53
|
delegate?: InterfaceProperty;
|
|
46
54
|
};
|
|
47
55
|
|
|
56
|
+
type DeclaredAccessor = AccessorBase & {
|
|
57
|
+
isInterfaceProperty?: false;
|
|
58
|
+
memberName: string;
|
|
59
|
+
hasGeneratedMember: boolean;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type InterfaceAccessor = AccessorBase & { isInterfaceProperty: true };
|
|
63
|
+
type PropertyAccessor = DeclaredAccessor | InterfaceAccessor;
|
|
48
64
|
/** A `GObject.ParamSpec` wrapper describing one property's name, type, flags and default. */
|
|
49
65
|
type PropertySpec = object;
|
|
50
66
|
|
|
@@ -72,13 +88,34 @@ const SET_PROPERTY_VFUNC = "vfuncSetProperty";
|
|
|
72
88
|
const READ_ONLY_REASON = "the property is read-only";
|
|
73
89
|
const CONSTRUCT_ONLY_REASON = "the property can only be set when the object is constructed";
|
|
74
90
|
const CLASS_T = structT("borrowed");
|
|
91
|
+
const CANONICAL_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
92
|
+
const DECLARED_NAMES = Symbol("gtkx:declaredPropertyNames");
|
|
93
|
+
|
|
94
|
+
const OVERRIDE_PARAM_T = fundamentalT(LIB, "g_param_spec_ref_sink", "g_param_spec_unref", {
|
|
95
|
+
ownership: "borrowed",
|
|
96
|
+
typeName: "GParam",
|
|
97
|
+
});
|
|
98
|
+
|
|
75
99
|
const interfaceStorages: Map<string, symbol> = new Map();
|
|
76
100
|
const paramSpecDefaultValue = bind(LIB, "g_param_spec_get_default_value", [PARAM_T], VALUE_T);
|
|
77
101
|
const paramSpecName = bind(LIB, "g_param_spec_get_name", [PARAM_T], stringT("borrowed"));
|
|
78
102
|
const typeClassRef = bind(LIB, "g_type_class_ref", [biguint64T], CLASS_T);
|
|
79
103
|
const typeClassUnref = bind(LIB, "g_type_class_unref", [CLASS_T], voidT);
|
|
104
|
+
const coercionChecks: Map<bigint, Map<string, PropertyCheck | null>> = new Map();
|
|
80
105
|
const classFindProperty = bind(LIB, "g_object_class_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
|
|
81
106
|
|
|
107
|
+
const classListProperties = bind(
|
|
108
|
+
LIB,
|
|
109
|
+
"g_object_class_list_properties",
|
|
110
|
+
[CLASS_T, refT(uint32T)],
|
|
111
|
+
sizedArrayT(PARAM_T, 1, "full"),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const defaultInterfaceRef = bind(LIB, "g_type_default_interface_ref", [biguint64T], CLASS_T);
|
|
115
|
+
const defaultInterfaceUnref = bind(LIB, "g_type_default_interface_unref", [CLASS_T], voidT);
|
|
116
|
+
const interfaceFindProperty = bind(LIB, "g_object_interface_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
|
|
117
|
+
const paramSpecOverrideNew = bind(LIB, "g_param_spec_override", [stringT("borrowed"), PARAM_T], OVERRIDE_PARAM_T);
|
|
118
|
+
|
|
82
119
|
const getPropertyName = (pspec: PropertySpec): string => paramSpecName(getHandle(pspec)) as string;
|
|
83
120
|
const underscoreCase = (name: string): string => name.replaceAll("-", "_");
|
|
84
121
|
const canonicalCase = (name: string): string => kebabCase(name).replaceAll("_", "-");
|
|
@@ -102,17 +139,85 @@ function checkFor(handle: ExternalObject<Handle>, name: string): PropertyCheck {
|
|
|
102
139
|
handle,
|
|
103
140
|
flags: getParamFlags(handle),
|
|
104
141
|
valueType,
|
|
105
|
-
canHoldValue: valueGuardFor(valueType),
|
|
142
|
+
canHoldValue: valueGuardOverrideFor(valueType) ?? valueGuardFor(valueType),
|
|
106
143
|
narrowValue: valueNarrowerFor(valueType),
|
|
107
144
|
};
|
|
108
145
|
}
|
|
109
146
|
|
|
147
|
+
function accessorSpecs(klass: ExternalObject<Handle>): Map<string, ExternalObject<Handle>> {
|
|
148
|
+
const specs: Map<string, ExternalObject<Handle>> = new Map();
|
|
149
|
+
const countRef = { value: 0 };
|
|
150
|
+
|
|
151
|
+
for (const pspec of classListProperties(klass, countRef) as ExternalObject<Handle>[]) {
|
|
152
|
+
specs.set(toCamelIdentifier(paramSpecName(pspec) as string), pspec);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return specs;
|
|
156
|
+
}
|
|
157
|
+
|
|
110
158
|
function findPropertySpec(klass: ExternalObject<Handle>, name: string): ExternalObject<Handle> | null {
|
|
111
|
-
const found = classFindProperty(klass, name)
|
|
159
|
+
const found = (classFindProperty(klass, name) ??
|
|
160
|
+
classFindProperty(klass, canonicalCase(name))) as ExternalObject<Handle> | null;
|
|
112
161
|
|
|
113
|
-
return found ?? (
|
|
162
|
+
return found ?? accessorSpecs(klass).get(toCamelIdentifier(name)) ?? null;
|
|
114
163
|
}
|
|
115
164
|
|
|
165
|
+
const sourceLabel = (source: bigint | AnyClass, gtype: bigint): string =>
|
|
166
|
+
typeName(gtype) ?? (typeof source === "function" ? source.name : String(source));
|
|
167
|
+
|
|
168
|
+
const findInterfaceSpec = (gtype: bigint, name: string): ExternalObject<Handle> | null => {
|
|
169
|
+
const vtable = defaultInterfaceRef(gtype) as ExternalObject<Handle>;
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
return interfaceFindProperty(vtable, name) as ExternalObject<Handle> | null;
|
|
173
|
+
} finally {
|
|
174
|
+
defaultInterfaceUnref(vtable);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const findClassSpec = (gtype: bigint, name: string): ExternalObject<Handle> | null => {
|
|
179
|
+
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
return classFindProperty(klass, name) as ExternalObject<Handle> | null;
|
|
183
|
+
} finally {
|
|
184
|
+
typeClassUnref(klass);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const findSourceSpec = (source: bigint | AnyClass, name: string): ExternalObject<Handle> => {
|
|
189
|
+
const gtype = typeof source === "bigint" ? source : getClassType(source);
|
|
190
|
+
const isInterface = typeIsA(gtype, TYPE_INTERFACE);
|
|
191
|
+
|
|
192
|
+
if (!isInterface && !typeIsA(gtype, TYPE_OBJECT)) {
|
|
193
|
+
throw new TypeError(
|
|
194
|
+
`paramSpecOverride: '${sourceLabel(source, gtype)}' is neither a registered object class nor an interface`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const pspec = isInterface ? findInterfaceSpec(gtype, name) : findClassSpec(gtype, name);
|
|
199
|
+
|
|
200
|
+
if (pspec === null) {
|
|
201
|
+
throw new TypeError(`paramSpecOverride: type '${sourceLabel(source, gtype)}' has no property named '${name}'`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return pspec;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Creates the handle of a `GParamSpec` that redirects to the property `source` declares under
|
|
209
|
+
* `name` — the equivalent of `g_param_spec_override`, which introspection does not expose.
|
|
210
|
+
*
|
|
211
|
+
* Throws when `source` is neither a GType nor a registered class or interface, and when it
|
|
212
|
+
* declares no property under `name`.
|
|
213
|
+
*
|
|
214
|
+
* @param name Canonical name of the property to override.
|
|
215
|
+
* @param source The GType, wrapper class or interface declaring the property.
|
|
216
|
+
* @returns Handle of the override spec.
|
|
217
|
+
*/
|
|
218
|
+
const newParamSpecOverride = (name: string, source: bigint | AnyClass): ExternalObject<Handle> =>
|
|
219
|
+
paramSpecOverrideNew(name, findSourceSpec(source, name)) as ExternalObject<Handle>;
|
|
220
|
+
|
|
116
221
|
function describeValue(value: unknown): string {
|
|
117
222
|
if (typeof value === "string") {
|
|
118
223
|
return JSON.stringify(value);
|
|
@@ -213,6 +318,113 @@ function constructValueFor(wrapper: object, check: PropertyCheck, value: unknown
|
|
|
213
318
|
return { name: check.propertyName, value: checkedValueFor(wrapper, check, value) };
|
|
214
319
|
}
|
|
215
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Whether the type declares a readable GObject property under `propertyName`, asking GObject itself
|
|
323
|
+
* rather than looking for a generated accessor, which codegen omits when a method shares the name.
|
|
324
|
+
*/
|
|
325
|
+
function isReadableProperty(gtype: bigint, propertyName: string): boolean {
|
|
326
|
+
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const pspec = findPropertySpec(klass, propertyName);
|
|
330
|
+
|
|
331
|
+
return pspec !== null && isParamReadable(getParamFlags(pspec));
|
|
332
|
+
} finally {
|
|
333
|
+
typeClassUnref(klass);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null {
|
|
338
|
+
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const pspec = findPropertySpec(klass, name);
|
|
342
|
+
|
|
343
|
+
return pspec === null ? null : checkFor(pspec, name);
|
|
344
|
+
} finally {
|
|
345
|
+
typeClassUnref(klass);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function coercionCheckFor(gtype: bigint, name: string): PropertyCheck | null {
|
|
350
|
+
let checks = coercionChecks.get(gtype);
|
|
351
|
+
|
|
352
|
+
if (checks === undefined) {
|
|
353
|
+
checks = new Map();
|
|
354
|
+
coercionChecks.set(gtype, checks);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let check = checks.get(name);
|
|
358
|
+
|
|
359
|
+
if (check === undefined) {
|
|
360
|
+
check = lookupCoercionCheck(gtype, name);
|
|
361
|
+
checks.set(name, check);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return check;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function truncateToWhole(check: PropertyCheck, value: number): number {
|
|
368
|
+
const range = WHOLE_NUMBER_RANGES.get(typeFundamental(check.valueType));
|
|
369
|
+
|
|
370
|
+
if (range === undefined) {
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return Math.min(range[1], Math.max(range[0], Math.trunc(value)));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function validatedNumber(check: PropertyCheck, number: number): number {
|
|
378
|
+
check.scratch ??= newValueForType(check.valueType);
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
writeHeld(check, check.scratch, number);
|
|
382
|
+
} catch {
|
|
383
|
+
return number;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return wasParamValueModified(check.handle, check.scratch) ? (fromValue(check.scratch) as number) : number;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function coerceNumber(check: PropertyCheck, value: number): number {
|
|
390
|
+
const number = truncateToWhole(check, value);
|
|
391
|
+
|
|
392
|
+
return check.canHoldValue(number) ? validatedNumber(check, number) : value;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function coercePropertyValue(gtype: bigint, propertyName: string, value: unknown): unknown {
|
|
396
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const check = coercionCheckFor(gtype, propertyName);
|
|
401
|
+
|
|
402
|
+
return check === null ? value : coerceNumber(check, value);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Fits a number to what a GObject property accepts, the way an animation writing a value on every
|
|
407
|
+
* frame needs: a fractional number headed for a property that holds whole numbers, such as a
|
|
408
|
+
* `gint`, an enum, or flags, is truncated toward zero, and a number outside the range the
|
|
409
|
+
* property's `GObject.ParamSpec` allows is clamped to it, so the write that follows never trips
|
|
410
|
+
* GObject's range check. Anything that is not a finite number, a number the property cannot hold
|
|
411
|
+
* at all, and a name the object installs no property under come back unchanged.
|
|
412
|
+
*
|
|
413
|
+
* @param obj The object the value is about to be written to.
|
|
414
|
+
* @param propertyName The property name, dashed or camelCased.
|
|
415
|
+
* @param value The value about to be written.
|
|
416
|
+
* @returns The value as the property accepts it.
|
|
417
|
+
*/
|
|
418
|
+
function coerceObjectProperty(obj: object, propertyName: string, value: unknown): unknown {
|
|
419
|
+
if (typeof value !== "number") {
|
|
420
|
+
return value;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const gtype = getInstanceType(obj);
|
|
424
|
+
|
|
425
|
+
return gtype === TYPE_INVALID ? value : coercePropertyValue(gtype, propertyName, value);
|
|
426
|
+
}
|
|
427
|
+
|
|
216
428
|
function constructPropertyFor(
|
|
217
429
|
gtype: bigint,
|
|
218
430
|
name: string,
|
|
@@ -268,13 +480,13 @@ function storedGetter(accessor: PropertyAccessor): (this: object) => unknown {
|
|
|
268
480
|
};
|
|
269
481
|
}
|
|
270
482
|
|
|
271
|
-
function memberGetter(accessor:
|
|
483
|
+
function memberGetter(accessor: DeclaredAccessor): (this: object) => unknown {
|
|
272
484
|
return function get(this: object) {
|
|
273
485
|
return (this as Record<string, unknown>)[accessor.memberName];
|
|
274
486
|
};
|
|
275
487
|
}
|
|
276
488
|
|
|
277
|
-
function memberSetter(accessor:
|
|
489
|
+
function memberSetter(accessor: DeclaredAccessor): (this: object, value: unknown) => void {
|
|
278
490
|
return function set(this: object, value: unknown) {
|
|
279
491
|
(this as Record<string, unknown>)[accessor.memberName] = value;
|
|
280
492
|
};
|
|
@@ -286,7 +498,7 @@ function refusalFor(accessor: PropertyAccessor, reason: string): (this: object,
|
|
|
286
498
|
};
|
|
287
499
|
}
|
|
288
500
|
|
|
289
|
-
function checkedSetter(accessor:
|
|
501
|
+
function checkedSetter(accessor: DeclaredAccessor): (this: object, value: unknown) => void {
|
|
290
502
|
if (!isParamWritable(accessor.flags)) {
|
|
291
503
|
return refusalFor(accessor, READ_ONLY_REASON);
|
|
292
504
|
}
|
|
@@ -312,7 +524,7 @@ function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias
|
|
|
312
524
|
Object.defineProperty(prototype, alias, descriptor);
|
|
313
525
|
}
|
|
314
526
|
|
|
315
|
-
function installAccessors(klass: AnyClass, accessor:
|
|
527
|
+
function installAccessors(klass: AnyClass, accessor: DeclaredAccessor): void {
|
|
316
528
|
const prototype = (klass as { prototype: object }).prototype;
|
|
317
529
|
accessor.hasGeneratedMember = !hasOwnMember(prototype, accessor.memberName);
|
|
318
530
|
|
|
@@ -341,16 +553,14 @@ function storageFor(propertyName: string): symbol {
|
|
|
341
553
|
return created;
|
|
342
554
|
}
|
|
343
555
|
|
|
344
|
-
function interfaceAccessorFor(dispatch: PropertyDispatch, pspec: PropertySpec):
|
|
556
|
+
function interfaceAccessorFor(dispatch: PropertyDispatch, pspec: PropertySpec): InterfaceAccessor {
|
|
345
557
|
const handle = getHandle(pspec);
|
|
346
558
|
const propertyName = paramSpecName(handle) as string;
|
|
347
559
|
const delegate = dispatch.delegates.get(propertyName);
|
|
348
560
|
|
|
349
|
-
const accessor:
|
|
561
|
+
const accessor: InterfaceAccessor = {
|
|
350
562
|
...checkFor(handle, propertyName),
|
|
351
|
-
memberName: camelCase(propertyName),
|
|
352
563
|
storage: storageFor(propertyName),
|
|
353
|
-
hasGeneratedMember: false,
|
|
354
564
|
isInterfaceProperty: true,
|
|
355
565
|
};
|
|
356
566
|
|
|
@@ -388,7 +598,7 @@ function callMember(instance: object, member: string, args: unknown[]): unknown
|
|
|
388
598
|
return (fn as (...values: unknown[]) => unknown).apply(instance, args);
|
|
389
599
|
}
|
|
390
600
|
|
|
391
|
-
function backingMemberFor(instance: object, accessor:
|
|
601
|
+
function backingMemberFor(instance: object, accessor: DeclaredAccessor): string | undefined {
|
|
392
602
|
if (accessor.hasGeneratedMember && !Object.hasOwn(instance, accessor.memberName)) {
|
|
393
603
|
return undefined;
|
|
394
604
|
}
|
|
@@ -414,6 +624,14 @@ function readCurrent(instance: object, accessor: PropertyAccessor): unknown {
|
|
|
414
624
|
: (instance as Record<string, unknown>)[member];
|
|
415
625
|
}
|
|
416
626
|
|
|
627
|
+
function storeCurrent(instance: Record<symbol, unknown>, accessor: PropertyAccessor, value: unknown): void {
|
|
628
|
+
if (isParamExplicitlyNotified(accessor.flags)) {
|
|
629
|
+
writeStored(instance, accessor, value);
|
|
630
|
+
} else {
|
|
631
|
+
storeValue(instance, accessor, value);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
417
635
|
function writeCurrent(instance: object, accessor: PropertyAccessor, value: unknown): void {
|
|
418
636
|
const setter = accessor.delegate?.setter;
|
|
419
637
|
|
|
@@ -432,7 +650,7 @@ function writeCurrent(instance: object, accessor: PropertyAccessor, value: unkno
|
|
|
432
650
|
const member = backingMemberFor(instance, accessor);
|
|
433
651
|
|
|
434
652
|
if (member === undefined) {
|
|
435
|
-
|
|
653
|
+
storeCurrent(instance as Record<symbol, unknown>, accessor, value);
|
|
436
654
|
|
|
437
655
|
return;
|
|
438
656
|
}
|
|
@@ -456,16 +674,17 @@ function makeSetProperty(dispatch: PropertyDispatch) {
|
|
|
456
674
|
};
|
|
457
675
|
}
|
|
458
676
|
|
|
459
|
-
|
|
460
|
-
|
|
677
|
+
const isCanonicalName = (name: string, propertyName: string): boolean =>
|
|
678
|
+
CANONICAL_NAME_PATTERN.test(propertyName) && toCamelIdentifier(propertyName) === toCamelIdentifier(name);
|
|
461
679
|
|
|
462
|
-
|
|
680
|
+
function assertCanonicalName(klass: AnyClass, name: string, propertyName: string): void {
|
|
681
|
+
if (isCanonicalName(name, propertyName)) {
|
|
463
682
|
return;
|
|
464
683
|
}
|
|
465
684
|
|
|
466
685
|
throw new TypeError(
|
|
467
686
|
`registerClass: ${klass.name} keys the property '${name}' to a GObject.ParamSpec named ` +
|
|
468
|
-
`'${propertyName}', which is the name GObject notifies under; name the ParamSpec '${
|
|
687
|
+
`'${propertyName}', which is the name GObject notifies under; name the ParamSpec '${canonicalCase(name)}'`,
|
|
469
688
|
);
|
|
470
689
|
}
|
|
471
690
|
|
|
@@ -475,8 +694,8 @@ function assertCanonicalNames(klass: AnyClass, properties: Record<string, Proper
|
|
|
475
694
|
}
|
|
476
695
|
}
|
|
477
696
|
|
|
478
|
-
function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec):
|
|
479
|
-
const accessor:
|
|
697
|
+
function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec): DeclaredAccessor {
|
|
698
|
+
const accessor: DeclaredAccessor = {
|
|
480
699
|
...checkFor(getHandle(pspec), name),
|
|
481
700
|
memberName: camelCase(name),
|
|
482
701
|
storage: Symbol(`gtkx:property:${name}`),
|
|
@@ -488,7 +707,7 @@ function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec): Prop
|
|
|
488
707
|
return accessor;
|
|
489
708
|
}
|
|
490
709
|
|
|
491
|
-
function buildAccessors(source: PropertyDispatchSource):
|
|
710
|
+
function buildAccessors(source: PropertyDispatchSource): DeclaredAccessor[] {
|
|
492
711
|
const { klass, properties } = source;
|
|
493
712
|
assertCanonicalNames(klass, properties);
|
|
494
713
|
|
|
@@ -515,11 +734,35 @@ function interfaceDelegatesFor(adoptedTypes: bigint[]): Map<string, InterfacePro
|
|
|
515
734
|
return delegates;
|
|
516
735
|
}
|
|
517
736
|
|
|
737
|
+
function recordDeclaredNames(klass: AnyClass, accessors: DeclaredAccessor[]): void {
|
|
738
|
+
const proto = klass.prototype as Record<PropertyKey, unknown>;
|
|
739
|
+
const inherited = proto[DECLARED_NAMES] as Record<string, string> | undefined;
|
|
740
|
+
const own: Record<string, string> = {};
|
|
741
|
+
|
|
742
|
+
for (const accessor of accessors) {
|
|
743
|
+
own[accessor.memberName] = accessor.propertyName;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
Object.defineProperty(proto, DECLARED_NAMES, { value: { ...inherited, ...own }, enumerable: false });
|
|
747
|
+
}
|
|
748
|
+
|
|
518
749
|
function buildPropertyDispatch(source: PropertyDispatchSource): PropertyDispatch {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
};
|
|
750
|
+
const declared = buildAccessors(source);
|
|
751
|
+
recordDeclaredNames(source.klass, declared);
|
|
752
|
+
|
|
753
|
+
return { accessors: [...declared], delegates: interfaceDelegatesFor(source.adoptedTypes) };
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* The GObject name a class registered with `registerClass` installed an accessor under, or
|
|
758
|
+
* `undefined` when the accessor names no property the class declared itself.
|
|
759
|
+
*/
|
|
760
|
+
function getDeclaredPropertyName(object: object, accessor: string): string | undefined {
|
|
761
|
+
const declared = (object as Record<PropertyKey, unknown>)[DECLARED_NAMES] as
|
|
762
|
+
| Record<string, string> |
|
|
763
|
+
undefined;
|
|
764
|
+
|
|
765
|
+
return declared?.[accessor];
|
|
523
766
|
}
|
|
524
767
|
|
|
525
768
|
function toNativeProperties(properties: Record<string, PropertySpec>): RegisterClassProperty[] {
|
|
@@ -531,10 +774,15 @@ function toNativeProperties(properties: Record<string, PropertySpec>): RegisterC
|
|
|
531
774
|
|
|
532
775
|
export {
|
|
533
776
|
buildPropertyDispatch,
|
|
777
|
+
coerceObjectProperty,
|
|
778
|
+
getDeclaredPropertyName,
|
|
779
|
+
isReadableProperty,
|
|
780
|
+
coercePropertyValue,
|
|
534
781
|
constructPropertyFor,
|
|
535
782
|
GET_PROPERTY_VFUNC,
|
|
536
783
|
makeGetProperty,
|
|
537
784
|
makeSetProperty,
|
|
785
|
+
newParamSpecOverride,
|
|
538
786
|
SET_PROPERTY_VFUNC,
|
|
539
787
|
toNativeProperties,
|
|
540
788
|
type ConstructProperty,
|
package/src/regex.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type Arg } from "./arg.js";
|
|
2
|
+
import { booleanT, boxedT, bufferT, int32T, int64T, uint32T } from "./descriptors.js";
|
|
3
|
+
import { fn } from "./fn.js";
|
|
4
|
+
import { LIB } from "./library.js";
|
|
5
|
+
import { getHandle } from "./registry.js";
|
|
6
|
+
|
|
7
|
+
type MatchParams = {
|
|
8
|
+
regex: object;
|
|
9
|
+
subject: string | string[];
|
|
10
|
+
startPosition: number;
|
|
11
|
+
matchOptions: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const MATCH_ARGS: Arg[] = [
|
|
15
|
+
{
|
|
16
|
+
type: boxedT("GRegex", { ownership: "borrowed", sharedLibrary: LIB, getTypeFnName: "g_regex_get_type" }),
|
|
17
|
+
},
|
|
18
|
+
{ type: bufferT },
|
|
19
|
+
{ type: int64T },
|
|
20
|
+
{ type: int32T },
|
|
21
|
+
{ type: uint32T },
|
|
22
|
+
{
|
|
23
|
+
type: boxedT("GMatchInfo", { ownership: "full", sharedLibrary: LIB, getTypeFnName: "g_match_info_get_type" }),
|
|
24
|
+
direction: "out",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const gRegexMatchFull = fn(LIB, "g_regex_match_full", { args: MATCH_ARGS, returns: booleanT, canThrow: true });
|
|
29
|
+
const gRegexMatchAllFull = fn(LIB, "g_regex_match_all_full", { args: MATCH_ARGS, returns: booleanT, canThrow: true });
|
|
30
|
+
/* eslint-disable-next-line sonarjs/no-unused-collection -- keeps each subject's bytes alive alongside its match info */
|
|
31
|
+
const subjectBytes: WeakMap<object, Uint8Array> = new WeakMap();
|
|
32
|
+
const encoder = new TextEncoder();
|
|
33
|
+
|
|
34
|
+
const flattenSubject = (subject: string | string[]): string =>
|
|
35
|
+
Array.isArray(subject) ? subject.join("") : subject;
|
|
36
|
+
|
|
37
|
+
const matchWithSubject = <MatchInfo extends object>(
|
|
38
|
+
boundFn: (...inputs: unknown[]) => unknown,
|
|
39
|
+
{ regex, subject, startPosition, matchOptions }: MatchParams,
|
|
40
|
+
): [boolean, MatchInfo] => {
|
|
41
|
+
const bytes = encoder.encode(`${flattenSubject(subject)}\0`);
|
|
42
|
+
|
|
43
|
+
const result = boundFn(getHandle(regex), bytes, bytes.length - 1, startPosition, matchOptions) as [
|
|
44
|
+
boolean,
|
|
45
|
+
MatchInfo,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
subjectBytes.set(result[1], bytes);
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Scans for a match of a compiled `GRegex` in the subject string, keeping the subject's bytes
|
|
55
|
+
* alive so the returned match info's fetch methods stay valid for as long as it is reachable.
|
|
56
|
+
*
|
|
57
|
+
* @param regex The compiled regex to scan with.
|
|
58
|
+
* @param subject The string to scan for matches.
|
|
59
|
+
* @param startPosition Starting index of the subject to match, in bytes.
|
|
60
|
+
* @param matchOptions Match options to apply.
|
|
61
|
+
* @returns Whether the subject matched, and the match info describing the match.
|
|
62
|
+
*/
|
|
63
|
+
function matchRegex<MatchInfo extends object>(
|
|
64
|
+
regex: object,
|
|
65
|
+
subject: string | string[],
|
|
66
|
+
startPosition: number,
|
|
67
|
+
matchOptions: number,
|
|
68
|
+
): [boolean, MatchInfo] {
|
|
69
|
+
return matchWithSubject(gRegexMatchFull, { regex, subject, startPosition, matchOptions });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Scans for all possible matches of a compiled `GRegex` in the subject string using the DFA
|
|
74
|
+
* algorithm, including overlapping matches, keeping the subject's bytes alive so the returned
|
|
75
|
+
* match info's fetch methods stay valid for as long as it is reachable.
|
|
76
|
+
*
|
|
77
|
+
* @param regex The compiled regex to scan with.
|
|
78
|
+
* @param subject The string to scan for matches.
|
|
79
|
+
* @param startPosition Starting index of the subject to match, in bytes.
|
|
80
|
+
* @param matchOptions Match options to apply.
|
|
81
|
+
* @returns Whether the subject matched, and the match info describing the matches.
|
|
82
|
+
*/
|
|
83
|
+
function matchAllRegex<MatchInfo extends object>(
|
|
84
|
+
regex: object,
|
|
85
|
+
subject: string | string[],
|
|
86
|
+
startPosition: number,
|
|
87
|
+
matchOptions: number,
|
|
88
|
+
): [boolean, MatchInfo] {
|
|
89
|
+
return matchWithSubject(gRegexMatchAllFull, { regex, subject, startPosition, matchOptions });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { matchAllRegex, matchRegex };
|