@gtkx/runtime 1.2.2 → 1.3.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 +2 -1
- 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/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 +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- 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 +31 -1
- package/dist/param-spec.d.ts.map +1 -1
- package/dist/param-spec.js +66 -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 +28 -1
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +139 -8
- 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 +154 -11
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +157 -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 +8 -1
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +41 -3
- package/dist/signal.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 +4 -4
- package/src/arg.ts +6 -1
- package/src/closure.ts +2 -14
- package/src/descriptors.ts +74 -21
- package/src/fn.ts +73 -17
- package/src/index.ts +31 -4
- package/src/mixin.ts +1 -1
- package/src/native-value.ts +146 -14
- package/src/object.ts +23 -11
- package/src/param-spec.ts +94 -8
- package/src/promisify.ts +27 -1
- package/src/properties.ts +184 -5
- package/src/regex.ts +92 -0
- package/src/register-class.ts +446 -38
- package/src/registry.ts +219 -4
- package/src/signal.ts +77 -0
- package/src/type.ts +7 -0
- package/src/value.ts +268 -14
- package/src/variant.ts +184 -53
package/src/object.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { type AnyClass, getParentClass } from "@gtkx/utils";
|
|
|
3
3
|
import { bind } from "./bind.js";
|
|
4
4
|
import { objectT, stringT, voidT } from "./descriptors.js";
|
|
5
5
|
import { LIB, VALUE_T } from "./library.js";
|
|
6
|
-
import { type ConstructProperty, constructPropertyFor } from "./properties.js";
|
|
6
|
+
import { coercePropertyValue, type ConstructProperty, constructPropertyFor } from "./properties.js";
|
|
7
7
|
import { getHandle, registerWrapper } from "./registry.js";
|
|
8
|
-
import {
|
|
8
|
+
import { fromValueForDescriptor, newValueForDescriptor, toValue } from "./value.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* One construct property a wrapper class accepts: the canonical `GObject` name it is set under,
|
|
@@ -110,7 +110,7 @@ function constructPropertyForEntry(
|
|
|
110
110
|
return constructPropertyFor(source.gtype, name, value, source.wrapper);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
return { name: binding[0], value: toValue(binding[1], value) };
|
|
113
|
+
return { name: binding[0], value: toValue(binding[1], coercePropertyValue(source.gtype, binding[0], value)) };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
@@ -123,17 +123,24 @@ function constructPropertyForEntry(
|
|
|
123
123
|
* for a read-only property and for a value of a type the property cannot hold, and
|
|
124
124
|
* a `RangeError` for a value the ParamSpec rejects. A value marshalled through a
|
|
125
125
|
* declared descriptor is converted rather than checked, so a `null` handed to a
|
|
126
|
-
* numeric one of those lands 0 rather than being refused
|
|
126
|
+
* numeric one of those lands 0 rather than being refused, and a number is fitted to
|
|
127
|
+
* the property first, as `coerceObjectProperty` does: truncated toward zero for a
|
|
128
|
+
* whole-number property and clamped to the range its `GObject.ParamSpec` allows.
|
|
127
129
|
* Properties whose value is `undefined` are skipped. A type registered with
|
|
128
130
|
* `registerClass` binds the wrapper before its `constructed` slot runs, so an
|
|
129
131
|
* override of that slot already sees a usable instance.
|
|
132
|
+
* When construction returns an object that already has a wrapper — it reached
|
|
133
|
+
* JavaScript and was wrapped before `g_object_new` returned, for example a
|
|
134
|
+
* `Gtk.Window` observed through the toplevels list — that existing wrapper is
|
|
135
|
+
* returned instead of binding `wrapper`, so both references stay one object.
|
|
130
136
|
*
|
|
131
137
|
* @param gtype The GType of the object to construct.
|
|
132
138
|
* @param props Property names mapped to the values to set them to.
|
|
133
139
|
* @param wrapper The wrapper instance to bind to the new object.
|
|
134
|
-
* @returns The
|
|
140
|
+
* @returns The wrapper bound to the constructed object: `wrapper` itself, or
|
|
141
|
+
* the wrapper the object already had.
|
|
135
142
|
*/
|
|
136
|
-
function newObjectWithProperties(gtype: bigint, props: object, wrapper:
|
|
143
|
+
function newObjectWithProperties<T extends object>(gtype: bigint, props: object, wrapper: T): T {
|
|
137
144
|
const names: string[] = [];
|
|
138
145
|
const values: ExternalObject<Handle>[] = [];
|
|
139
146
|
const bindings = constructBindingsFor(wrapper.constructor as AnyClass | undefined);
|
|
@@ -148,9 +155,13 @@ function newObjectWithProperties(gtype: bigint, props: object, wrapper: object):
|
|
|
148
155
|
}
|
|
149
156
|
}
|
|
150
157
|
|
|
151
|
-
newObject(gtype, names, values, wrapper, registerWrapper);
|
|
158
|
+
const existing = newObject(gtype, names, values, wrapper, registerWrapper);
|
|
152
159
|
|
|
153
|
-
|
|
160
|
+
if (existing !== null) {
|
|
161
|
+
return existing as T;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return wrapper;
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
/**
|
|
@@ -165,15 +176,16 @@ function getObjectProperty(obj: object, propertyName: string, descriptor: Descri
|
|
|
165
176
|
const value = newValueForDescriptor(descriptor);
|
|
166
177
|
gObjectGetProperty(getHandle(obj), propertyName, value);
|
|
167
178
|
|
|
168
|
-
return
|
|
179
|
+
return fromValueForDescriptor(descriptor, value);
|
|
169
180
|
}
|
|
170
181
|
|
|
171
182
|
/**
|
|
172
183
|
* Writes a JavaScript value to a GObject property, converting it to native form
|
|
173
184
|
* using the descriptor. The descriptor converts what it is given rather than checking it against
|
|
174
185
|
* the property's `GObject.ParamSpec`, so `null` and `undefined` written to a numeric or enum
|
|
175
|
-
* property land 0,
|
|
176
|
-
* refused with a
|
|
186
|
+
* property land 0, and a fractional number written to a whole-number property is truncated toward
|
|
187
|
+
* zero, where the same writes to a property installed through `registerClass` are refused with a
|
|
188
|
+
* `TypeError`.
|
|
177
189
|
*
|
|
178
190
|
* @param obj The object to write to.
|
|
179
191
|
* @param propertyName The property name.
|
package/src/param-spec.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type ExternalObject, type Handle, read } from "@gtkx/native";
|
|
|
2
2
|
import { bind } from "./bind.js";
|
|
3
3
|
import { biguint64T, booleanT, stringT, uint32T, voidT } from "./descriptors.js";
|
|
4
4
|
import { LIB, PARAM_T, VALUE_T } from "./library.js";
|
|
5
|
-
import { getInstanceType } from "./registry.js";
|
|
5
|
+
import { getHandle, getInstanceType } from "./registry.js";
|
|
6
6
|
import {
|
|
7
7
|
getStrvType,
|
|
8
8
|
isTypedClass,
|
|
@@ -33,15 +33,17 @@ import {
|
|
|
33
33
|
} from "./type.js";
|
|
34
34
|
|
|
35
35
|
type ValueGuard = (value: unknown) => boolean;
|
|
36
|
-
type ParamLayout = { flags: number; valueType: bigint };
|
|
36
|
+
type ParamLayout = { flags: number; valueType: bigint; ownerType: bigint };
|
|
37
37
|
|
|
38
38
|
const PARAM_READABLE = 1;
|
|
39
39
|
const PARAM_WRITABLE = 2;
|
|
40
40
|
const PARAM_CONSTRUCT_ONLY = 8;
|
|
41
41
|
const PARAM_LAX_VALIDATION = 16;
|
|
42
|
+
const PARAM_EXPLICIT_NOTIFY = 1 << 30;
|
|
42
43
|
const READ_FLAGS = PARAM_READABLE | PARAM_WRITABLE | PARAM_CONSTRUCT_ONLY | PARAM_LAX_VALIDATION;
|
|
43
44
|
const FLAGS_BYTE_OFFSET = 16;
|
|
44
45
|
const VALUE_TYPE_BYTE_OFFSET = 24;
|
|
46
|
+
const OWNER_TYPE_BYTE_OFFSET = 32;
|
|
45
47
|
const LAYOUT_PROBE_NAME = "gtkx-param-layout";
|
|
46
48
|
const INT8_MINIMUM = -128;
|
|
47
49
|
const INT8_MAXIMUM = 127;
|
|
@@ -49,9 +51,9 @@ const UINT8_MAXIMUM = 255;
|
|
|
49
51
|
const INT32_MINIMUM = -2_147_483_648;
|
|
50
52
|
const INT32_MAXIMUM = 2_147_483_647;
|
|
51
53
|
const UINT32_MAXIMUM = 4_294_967_295;
|
|
52
|
-
const INT64_MINIMUM = -(2n ** 63n);
|
|
53
|
-
const INT64_MAXIMUM = 2n ** 63n - 1n;
|
|
54
|
-
const UINT64_MAXIMUM = 2n ** 64n - 1n;
|
|
54
|
+
const INT64_MINIMUM: bigint = -(2n ** 63n);
|
|
55
|
+
const INT64_MAXIMUM: bigint = 2n ** 63n - 1n;
|
|
56
|
+
const UINT64_MAXIMUM: bigint = 2n ** 64n - 1n;
|
|
55
57
|
|
|
56
58
|
const WRAPPED_FUNDAMENTALS: Set<bigint> = new Set([
|
|
57
59
|
TYPE_BOXED,
|
|
@@ -62,6 +64,20 @@ const WRAPPED_FUNDAMENTALS: Set<bigint> = new Set([
|
|
|
62
64
|
]);
|
|
63
65
|
|
|
64
66
|
const isWideUnsignedValue: ValueGuard = wideIntegerGuardFor(0n, UINT64_MAXIMUM);
|
|
67
|
+
const MAX_SAFE: number = Number.MAX_SAFE_INTEGER;
|
|
68
|
+
|
|
69
|
+
const WHOLE_NUMBER_RANGES: Map<bigint, [minimum: number, maximum: number]> = new Map([
|
|
70
|
+
[TYPE_CHAR, [INT8_MINIMUM, INT8_MAXIMUM]],
|
|
71
|
+
[TYPE_UCHAR, [0, UINT8_MAXIMUM]],
|
|
72
|
+
[TYPE_INT, [INT32_MINIMUM, INT32_MAXIMUM]],
|
|
73
|
+
[TYPE_UINT, [0, UINT32_MAXIMUM]],
|
|
74
|
+
[TYPE_ENUM, [INT32_MINIMUM, INT32_MAXIMUM]],
|
|
75
|
+
[TYPE_FLAGS, [0, UINT32_MAXIMUM]],
|
|
76
|
+
[TYPE_LONG, [-MAX_SAFE, MAX_SAFE]],
|
|
77
|
+
[TYPE_ULONG, [0, MAX_SAFE]],
|
|
78
|
+
[TYPE_INT64, [-MAX_SAFE, MAX_SAFE]],
|
|
79
|
+
[TYPE_UINT64, [0, MAX_SAFE]],
|
|
80
|
+
]);
|
|
65
81
|
|
|
66
82
|
const SCALAR_GUARDS: Map<bigint, ValueGuard> = new Map([
|
|
67
83
|
[TYPE_BOOLEAN, isBooleanValue],
|
|
@@ -98,9 +114,49 @@ const getParamFlags = (pspec: ExternalObject<Handle>): number => read(pspec, uin
|
|
|
98
114
|
const getParamValueType = (pspec: ExternalObject<Handle>): bigint =>
|
|
99
115
|
read(pspec, biguint64T, VALUE_TYPE_BYTE_OFFSET) as bigint;
|
|
100
116
|
|
|
117
|
+
const getParamOwnerType = (pspec: ExternalObject<Handle>): bigint =>
|
|
118
|
+
read(pspec, biguint64T, OWNER_TYPE_BYTE_OFFSET) as bigint;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Reads the `GParamFlags` bitfield of a `GObject.ParamSpec`.
|
|
122
|
+
*
|
|
123
|
+
* @param spec The param spec wrapper to read.
|
|
124
|
+
* @returns The spec's flags.
|
|
125
|
+
*/
|
|
126
|
+
const getParamSpecFlags = (spec: object): number => {
|
|
127
|
+
assertParamLayout();
|
|
128
|
+
|
|
129
|
+
return getParamFlags(getHandle(spec));
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reads the GType of the values a `GObject.ParamSpec` describes.
|
|
134
|
+
*
|
|
135
|
+
* @param spec The param spec wrapper to read.
|
|
136
|
+
* @returns The GType of the spec's values.
|
|
137
|
+
*/
|
|
138
|
+
const getParamSpecValueType = (spec: object): bigint => {
|
|
139
|
+
assertParamLayout();
|
|
140
|
+
|
|
141
|
+
return getParamValueType(getHandle(spec));
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Reads the GType a `GObject.ParamSpec` is installed on.
|
|
146
|
+
*
|
|
147
|
+
* @param spec The param spec wrapper to read.
|
|
148
|
+
* @returns The owning GType, or `TYPE_INVALID` while the spec is not installed on any type.
|
|
149
|
+
*/
|
|
150
|
+
const getParamSpecOwnerType = (spec: object): bigint => {
|
|
151
|
+
assertParamLayout();
|
|
152
|
+
|
|
153
|
+
return getParamOwnerType(getHandle(spec));
|
|
154
|
+
};
|
|
155
|
+
|
|
101
156
|
const isParamWritable = (flags: number): boolean => (flags & PARAM_WRITABLE) !== 0;
|
|
102
157
|
const isParamConstructOnly = (flags: number): boolean => (flags & PARAM_CONSTRUCT_ONLY) !== 0;
|
|
103
158
|
const isParamLaxlyValidated = (flags: number): boolean => (flags & PARAM_LAX_VALIDATION) !== 0;
|
|
159
|
+
const isParamExplicitlyNotified = (flags: number): boolean => (flags & PARAM_EXPLICIT_NOTIFY) !== 0;
|
|
104
160
|
|
|
105
161
|
const wasParamValueModified = (pspec: ExternalObject<Handle>, value: ExternalObject<Handle>): boolean =>
|
|
106
162
|
paramValueValidate(pspec, value) as boolean;
|
|
@@ -113,8 +169,18 @@ function isStringValue(value: unknown): boolean {
|
|
|
113
169
|
return value == null || typeof value === "string";
|
|
114
170
|
}
|
|
115
171
|
|
|
172
|
+
function isStringArray(value: unknown[]): boolean {
|
|
173
|
+
for (const item of value) {
|
|
174
|
+
if (typeof item !== "string") {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
116
182
|
function isStrvValue(value: unknown): boolean {
|
|
117
|
-
return value == null || (Array.isArray(value) && value
|
|
183
|
+
return value == null || (Array.isArray(value) && isStringArray(value));
|
|
118
184
|
}
|
|
119
185
|
|
|
120
186
|
function isNumberValue(value: unknown): boolean {
|
|
@@ -193,7 +259,11 @@ function readProbeLayout(flags: number): ParamLayout {
|
|
|
193
259
|
paramSpecRefSink(probe);
|
|
194
260
|
|
|
195
261
|
try {
|
|
196
|
-
return {
|
|
262
|
+
return {
|
|
263
|
+
flags: getParamFlags(probe),
|
|
264
|
+
valueType: getParamValueType(probe),
|
|
265
|
+
ownerType: getParamOwnerType(probe),
|
|
266
|
+
};
|
|
197
267
|
} finally {
|
|
198
268
|
paramSpecUnref(probe);
|
|
199
269
|
}
|
|
@@ -202,7 +272,11 @@ function readProbeLayout(flags: number): ParamLayout {
|
|
|
202
272
|
function isLayoutIntact(flags: number): boolean {
|
|
203
273
|
const layout = readProbeLayout(flags);
|
|
204
274
|
|
|
205
|
-
return (
|
|
275
|
+
return (
|
|
276
|
+
(layout.flags & READ_FLAGS) === flags &&
|
|
277
|
+
layout.valueType === TYPE_BOOLEAN &&
|
|
278
|
+
layout.ownerType === TYPE_INVALID
|
|
279
|
+
);
|
|
206
280
|
}
|
|
207
281
|
|
|
208
282
|
function assertParamLayout(): void {
|
|
@@ -223,11 +297,23 @@ function assertParamLayout(): void {
|
|
|
223
297
|
}
|
|
224
298
|
|
|
225
299
|
export {
|
|
300
|
+
WHOLE_NUMBER_RANGES,
|
|
226
301
|
getParamFlags,
|
|
302
|
+
getParamSpecFlags,
|
|
303
|
+
getParamSpecOwnerType,
|
|
304
|
+
getParamSpecValueType,
|
|
227
305
|
getParamValueType,
|
|
306
|
+
INT32_MAXIMUM,
|
|
307
|
+
INT32_MINIMUM,
|
|
308
|
+
INT64_MAXIMUM,
|
|
309
|
+
INT64_MINIMUM,
|
|
228
310
|
isParamConstructOnly,
|
|
311
|
+
isStringArray,
|
|
312
|
+
isParamExplicitlyNotified,
|
|
229
313
|
isParamLaxlyValidated,
|
|
230
314
|
isParamWritable,
|
|
315
|
+
resolveGtype,
|
|
316
|
+
UINT64_MAXIMUM,
|
|
231
317
|
type ValueGuard,
|
|
232
318
|
valueGuardFor,
|
|
233
319
|
wasParamValueModified,
|
package/src/promisify.ts
CHANGED
|
@@ -13,6 +13,13 @@ type Settlement<R extends object, T> = {
|
|
|
13
13
|
reject: (reason: Error) => void;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
/** A finish result with its leading success boolean dropped: bare when one value remains, a tuple otherwise. */
|
|
17
|
+
type TrimmedFinish<T> = T extends readonly [boolean, infer Only]
|
|
18
|
+
? Only
|
|
19
|
+
: T extends readonly [boolean, ...infer Rest]
|
|
20
|
+
? Rest
|
|
21
|
+
: never;
|
|
22
|
+
|
|
16
23
|
const attachCreationStack = (error: unknown, creationStack: Error | undefined): void => {
|
|
17
24
|
if (creationStack === undefined || !(error instanceof Error)) {
|
|
18
25
|
return;
|
|
@@ -36,6 +43,25 @@ const settle = <R extends object, T>(settlement: Settlement<R, T>, asyncResult:
|
|
|
36
43
|
}
|
|
37
44
|
};
|
|
38
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Wraps a finish function whose result leads with the success boolean of a throwing C call,
|
|
48
|
+
* dropping that boolean: failure already surfaces as a thrown error, so the boolean is always
|
|
49
|
+
* `true`. A single remaining value is handed back bare, several stay a tuple. The finish result
|
|
50
|
+
* must carry at least one value beyond the boolean; a boolean-only finish needs no trimming.
|
|
51
|
+
*
|
|
52
|
+
* @param finish Extracts the boolean-led result from the async result passed to the completion callback.
|
|
53
|
+
* @returns A finish function resolving to the result without its leading boolean.
|
|
54
|
+
*/
|
|
55
|
+
const trimFinish =
|
|
56
|
+
<R extends object, T extends readonly [boolean, unknown, ...unknown[]]>(
|
|
57
|
+
finish: FinishResult<R, T>,
|
|
58
|
+
): FinishResult<R, TrimmedFinish<T>> =>
|
|
59
|
+
(result: R): TrimmedFinish<T> => {
|
|
60
|
+
const [, ...rest] = finish(result);
|
|
61
|
+
|
|
62
|
+
return (rest.length === 1 ? rest[0] : rest) as TrimmedFinish<T>;
|
|
63
|
+
};
|
|
64
|
+
|
|
39
65
|
/**
|
|
40
66
|
* Wraps a GIO-style asynchronous function that takes a completion callback into a
|
|
41
67
|
* promise, invoking the finish function to extract the result. Outside production,
|
|
@@ -64,4 +90,4 @@ const promisify = <R extends object, T>(
|
|
|
64
90
|
});
|
|
65
91
|
});
|
|
66
92
|
|
|
67
|
-
export { promisify };
|
|
93
|
+
export { promisify, trimFinish };
|
package/src/properties.ts
CHANGED
|
@@ -2,23 +2,33 @@ import type { ExternalObject, Handle, RegisterClassProperty } from "@gtkx/native
|
|
|
2
2
|
import type { AnyClass } from "@gtkx/utils";
|
|
3
3
|
import { camelCase, kebabCase } from "@gtkx/utils";
|
|
4
4
|
import { bind } from "./bind.js";
|
|
5
|
-
import { biguint64T, stringT, structT, voidT } from "./descriptors.js";
|
|
5
|
+
import { biguint64T, fundamentalT, stringT, structT, 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,
|
|
12
13
|
isParamWritable,
|
|
13
14
|
type ValueGuard,
|
|
14
15
|
valueGuardFor,
|
|
15
16
|
wasParamValueModified,
|
|
17
|
+
WHOLE_NUMBER_RANGES,
|
|
16
18
|
} from "./param-spec.js";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
+
import {
|
|
20
|
+
getClassType,
|
|
21
|
+
getHandle,
|
|
22
|
+
getInstanceType,
|
|
23
|
+
getInterfaceProperties,
|
|
24
|
+
instanceClassName,
|
|
25
|
+
type InterfaceProperty,
|
|
26
|
+
} from "./registry.js";
|
|
27
|
+
import { TYPE_INTERFACE, TYPE_INVALID, TYPE_OBJECT, typeFundamental, typeIsA, typeName } from "./type.js";
|
|
19
28
|
import {
|
|
20
29
|
fromValue,
|
|
21
30
|
newValueForType,
|
|
31
|
+
valueGuardOverrideFor,
|
|
22
32
|
type ValueNarrower,
|
|
23
33
|
valueNarrowerFor,
|
|
24
34
|
type ValueWriter,
|
|
@@ -72,12 +82,23 @@ const SET_PROPERTY_VFUNC = "vfuncSetProperty";
|
|
|
72
82
|
const READ_ONLY_REASON = "the property is read-only";
|
|
73
83
|
const CONSTRUCT_ONLY_REASON = "the property can only be set when the object is constructed";
|
|
74
84
|
const CLASS_T = structT("borrowed");
|
|
85
|
+
|
|
86
|
+
const OVERRIDE_PARAM_T = fundamentalT(LIB, "g_param_spec_ref_sink", "g_param_spec_unref", {
|
|
87
|
+
ownership: "borrowed",
|
|
88
|
+
typeName: "GParam",
|
|
89
|
+
});
|
|
90
|
+
|
|
75
91
|
const interfaceStorages: Map<string, symbol> = new Map();
|
|
76
92
|
const paramSpecDefaultValue = bind(LIB, "g_param_spec_get_default_value", [PARAM_T], VALUE_T);
|
|
77
93
|
const paramSpecName = bind(LIB, "g_param_spec_get_name", [PARAM_T], stringT("borrowed"));
|
|
78
94
|
const typeClassRef = bind(LIB, "g_type_class_ref", [biguint64T], CLASS_T);
|
|
79
95
|
const typeClassUnref = bind(LIB, "g_type_class_unref", [CLASS_T], voidT);
|
|
96
|
+
const coercionChecks: Map<bigint, Map<string, PropertyCheck | null>> = new Map();
|
|
80
97
|
const classFindProperty = bind(LIB, "g_object_class_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
|
|
98
|
+
const defaultInterfaceRef = bind(LIB, "g_type_default_interface_ref", [biguint64T], CLASS_T);
|
|
99
|
+
const defaultInterfaceUnref = bind(LIB, "g_type_default_interface_unref", [CLASS_T], voidT);
|
|
100
|
+
const interfaceFindProperty = bind(LIB, "g_object_interface_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
|
|
101
|
+
const paramSpecOverrideNew = bind(LIB, "g_param_spec_override", [stringT("borrowed"), PARAM_T], OVERRIDE_PARAM_T);
|
|
81
102
|
|
|
82
103
|
const getPropertyName = (pspec: PropertySpec): string => paramSpecName(getHandle(pspec)) as string;
|
|
83
104
|
const underscoreCase = (name: string): string => name.replaceAll("-", "_");
|
|
@@ -102,7 +123,7 @@ function checkFor(handle: ExternalObject<Handle>, name: string): PropertyCheck {
|
|
|
102
123
|
handle,
|
|
103
124
|
flags: getParamFlags(handle),
|
|
104
125
|
valueType,
|
|
105
|
-
canHoldValue: valueGuardFor(valueType),
|
|
126
|
+
canHoldValue: valueGuardOverrideFor(valueType) ?? valueGuardFor(valueType),
|
|
106
127
|
narrowValue: valueNarrowerFor(valueType),
|
|
107
128
|
};
|
|
108
129
|
}
|
|
@@ -113,6 +134,62 @@ function findPropertySpec(klass: ExternalObject<Handle>, name: string): External
|
|
|
113
134
|
return found ?? (classFindProperty(klass, canonicalCase(name)) as ExternalObject<Handle> | null);
|
|
114
135
|
}
|
|
115
136
|
|
|
137
|
+
const sourceLabel = (source: bigint | AnyClass, gtype: bigint): string =>
|
|
138
|
+
typeName(gtype) ?? (typeof source === "function" ? source.name : String(source));
|
|
139
|
+
|
|
140
|
+
const findInterfaceSpec = (gtype: bigint, name: string): ExternalObject<Handle> | null => {
|
|
141
|
+
const vtable = defaultInterfaceRef(gtype) as ExternalObject<Handle>;
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
return interfaceFindProperty(vtable, name) as ExternalObject<Handle> | null;
|
|
145
|
+
} finally {
|
|
146
|
+
defaultInterfaceUnref(vtable);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const findClassSpec = (gtype: bigint, name: string): ExternalObject<Handle> | null => {
|
|
151
|
+
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
return classFindProperty(klass, name) as ExternalObject<Handle> | null;
|
|
155
|
+
} finally {
|
|
156
|
+
typeClassUnref(klass);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const findSourceSpec = (source: bigint | AnyClass, name: string): ExternalObject<Handle> => {
|
|
161
|
+
const gtype = typeof source === "bigint" ? source : getClassType(source);
|
|
162
|
+
const isInterface = typeIsA(gtype, TYPE_INTERFACE);
|
|
163
|
+
|
|
164
|
+
if (!isInterface && !typeIsA(gtype, TYPE_OBJECT)) {
|
|
165
|
+
throw new TypeError(
|
|
166
|
+
`paramSpecOverride: '${sourceLabel(source, gtype)}' is neither a registered object class nor an interface`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const pspec = isInterface ? findInterfaceSpec(gtype, name) : findClassSpec(gtype, name);
|
|
171
|
+
|
|
172
|
+
if (pspec === null) {
|
|
173
|
+
throw new TypeError(`paramSpecOverride: type '${sourceLabel(source, gtype)}' has no property named '${name}'`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return pspec;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Creates the handle of a `GParamSpec` that redirects to the property `source` declares under
|
|
181
|
+
* `name` — the equivalent of `g_param_spec_override`, which introspection does not expose.
|
|
182
|
+
*
|
|
183
|
+
* Throws when `source` is neither a GType nor a registered class or interface, and when it
|
|
184
|
+
* declares no property under `name`.
|
|
185
|
+
*
|
|
186
|
+
* @param name Canonical name of the property to override.
|
|
187
|
+
* @param source The GType, wrapper class or interface declaring the property.
|
|
188
|
+
* @returns Handle of the override spec.
|
|
189
|
+
*/
|
|
190
|
+
const newParamSpecOverride = (name: string, source: bigint | AnyClass): ExternalObject<Handle> =>
|
|
191
|
+
paramSpecOverrideNew(name, findSourceSpec(source, name)) as ExternalObject<Handle>;
|
|
192
|
+
|
|
116
193
|
function describeValue(value: unknown): string {
|
|
117
194
|
if (typeof value === "string") {
|
|
118
195
|
return JSON.stringify(value);
|
|
@@ -213,6 +290,97 @@ function constructValueFor(wrapper: object, check: PropertyCheck, value: unknown
|
|
|
213
290
|
return { name: check.propertyName, value: checkedValueFor(wrapper, check, value) };
|
|
214
291
|
}
|
|
215
292
|
|
|
293
|
+
function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null {
|
|
294
|
+
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const pspec = findPropertySpec(klass, name);
|
|
298
|
+
|
|
299
|
+
return pspec === null ? null : checkFor(pspec, name);
|
|
300
|
+
} finally {
|
|
301
|
+
typeClassUnref(klass);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function coercionCheckFor(gtype: bigint, name: string): PropertyCheck | null {
|
|
306
|
+
let checks = coercionChecks.get(gtype);
|
|
307
|
+
|
|
308
|
+
if (checks === undefined) {
|
|
309
|
+
checks = new Map();
|
|
310
|
+
coercionChecks.set(gtype, checks);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let check = checks.get(name);
|
|
314
|
+
|
|
315
|
+
if (check === undefined) {
|
|
316
|
+
check = lookupCoercionCheck(gtype, name);
|
|
317
|
+
checks.set(name, check);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return check;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function truncateToWhole(check: PropertyCheck, value: number): number {
|
|
324
|
+
const range = WHOLE_NUMBER_RANGES.get(typeFundamental(check.valueType));
|
|
325
|
+
|
|
326
|
+
if (range === undefined) {
|
|
327
|
+
return value;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return Math.min(range[1], Math.max(range[0], Math.trunc(value)));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function validatedNumber(check: PropertyCheck, number: number): number {
|
|
334
|
+
check.scratch ??= newValueForType(check.valueType);
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
writeHeld(check, check.scratch, number);
|
|
338
|
+
} catch {
|
|
339
|
+
return number;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return wasParamValueModified(check.handle, check.scratch) ? (fromValue(check.scratch) as number) : number;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function coerceNumber(check: PropertyCheck, value: number): number {
|
|
346
|
+
const number = truncateToWhole(check, value);
|
|
347
|
+
|
|
348
|
+
return check.canHoldValue(number) ? validatedNumber(check, number) : value;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function coercePropertyValue(gtype: bigint, propertyName: string, value: unknown): unknown {
|
|
352
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const check = coercionCheckFor(gtype, propertyName);
|
|
357
|
+
|
|
358
|
+
return check === null ? value : coerceNumber(check, value);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Fits a number to what a GObject property accepts, the way an animation writing a value on every
|
|
363
|
+
* frame needs: a fractional number headed for a property that holds whole numbers, such as a
|
|
364
|
+
* `gint`, an enum, or flags, is truncated toward zero, and a number outside the range the
|
|
365
|
+
* property's `GObject.ParamSpec` allows is clamped to it, so the write that follows never trips
|
|
366
|
+
* GObject's range check. Anything that is not a finite number, a number the property cannot hold
|
|
367
|
+
* at all, and a name the object installs no property under come back unchanged.
|
|
368
|
+
*
|
|
369
|
+
* @param obj The object the value is about to be written to.
|
|
370
|
+
* @param propertyName The property name, dashed or camelCased.
|
|
371
|
+
* @param value The value about to be written.
|
|
372
|
+
* @returns The value as the property accepts it.
|
|
373
|
+
*/
|
|
374
|
+
function coerceObjectProperty(obj: object, propertyName: string, value: unknown): unknown {
|
|
375
|
+
if (typeof value !== "number") {
|
|
376
|
+
return value;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const gtype = getInstanceType(obj);
|
|
380
|
+
|
|
381
|
+
return gtype === TYPE_INVALID ? value : coercePropertyValue(gtype, propertyName, value);
|
|
382
|
+
}
|
|
383
|
+
|
|
216
384
|
function constructPropertyFor(
|
|
217
385
|
gtype: bigint,
|
|
218
386
|
name: string,
|
|
@@ -414,6 +582,14 @@ function readCurrent(instance: object, accessor: PropertyAccessor): unknown {
|
|
|
414
582
|
: (instance as Record<string, unknown>)[member];
|
|
415
583
|
}
|
|
416
584
|
|
|
585
|
+
function storeCurrent(instance: Record<symbol, unknown>, accessor: PropertyAccessor, value: unknown): void {
|
|
586
|
+
if (isParamExplicitlyNotified(accessor.flags)) {
|
|
587
|
+
writeStored(instance, accessor, value);
|
|
588
|
+
} else {
|
|
589
|
+
storeValue(instance, accessor, value);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
417
593
|
function writeCurrent(instance: object, accessor: PropertyAccessor, value: unknown): void {
|
|
418
594
|
const setter = accessor.delegate?.setter;
|
|
419
595
|
|
|
@@ -432,7 +608,7 @@ function writeCurrent(instance: object, accessor: PropertyAccessor, value: unkno
|
|
|
432
608
|
const member = backingMemberFor(instance, accessor);
|
|
433
609
|
|
|
434
610
|
if (member === undefined) {
|
|
435
|
-
|
|
611
|
+
storeCurrent(instance as Record<symbol, unknown>, accessor, value);
|
|
436
612
|
|
|
437
613
|
return;
|
|
438
614
|
}
|
|
@@ -531,10 +707,13 @@ function toNativeProperties(properties: Record<string, PropertySpec>): RegisterC
|
|
|
531
707
|
|
|
532
708
|
export {
|
|
533
709
|
buildPropertyDispatch,
|
|
710
|
+
coerceObjectProperty,
|
|
711
|
+
coercePropertyValue,
|
|
534
712
|
constructPropertyFor,
|
|
535
713
|
GET_PROPERTY_VFUNC,
|
|
536
714
|
makeGetProperty,
|
|
537
715
|
makeSetProperty,
|
|
716
|
+
newParamSpecOverride,
|
|
538
717
|
SET_PROPERTY_VFUNC,
|
|
539
718
|
toNativeProperties,
|
|
540
719
|
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 };
|