@astrale-os/kernel-dsl 0.2.0-beta.41 → 0.2.0-beta.42
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.
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { definitionRefIndex, compareDefinitionRefs } from '../../../addressing/ordering.js';
|
|
3
3
|
import { policyObjectKey, policyVariableSelector } from '../model/policy.js';
|
|
4
|
-
const textEncoder = new TextEncoder();
|
|
5
4
|
export function normalizePolicyExpression(value) {
|
|
6
5
|
if ('match' in value) {
|
|
7
6
|
return { match: normalizePolicyPattern(value.match) };
|
|
@@ -73,20 +72,22 @@ function normalizePatternNode(value) {
|
|
|
73
72
|
function canonicalVariableIds(value) {
|
|
74
73
|
const declarations = new Map();
|
|
75
74
|
collectDeclarations(value, declarations);
|
|
76
|
-
const
|
|
75
|
+
const uses = new Map([...declarations.keys()].map((id) => [id, []]));
|
|
76
|
+
collectUses(value, uses);
|
|
77
|
+
let colors = declarations;
|
|
78
|
+
// Color refinement. Each round names a variable by its declaration and the sorted colors of its
|
|
79
|
+
// uses, then replaces every name by its fixed-width rank among the round's names. Ranks keep the
|
|
80
|
+
// names' byte order, so a round decides what it would decide over uncompressed names, while a
|
|
81
|
+
// name stays linear in its variable's degree instead of embedding every neighbour's name. A
|
|
82
|
+
// round that reproduces its input is a fixed point of the next ones.
|
|
77
83
|
for (let iteration = 0; iteration < declarations.size + 1; iteration++) {
|
|
78
|
-
const next = new Map()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
uses.sort(compareUtf8Bytes);
|
|
83
|
-
next.set(id, `${declarations.get(id) ?? ''}|${uses.join('|')}`);
|
|
84
|
-
}
|
|
84
|
+
const next = rank(new Map([...uses].map(([id, list]) => {
|
|
85
|
+
const named = list.map(([prefix, term]) => `${prefix}${termColor(term, colors)}`);
|
|
86
|
+
return [id, `${declarations.get(id) ?? ''}|${named.sort(compareUtf8Bytes).join('|')}`];
|
|
87
|
+
})));
|
|
85
88
|
if ([...next].every(([id, color]) => colors.get(id) === color))
|
|
86
89
|
break;
|
|
87
|
-
colors
|
|
88
|
-
for (const [id, color] of next)
|
|
89
|
-
colors.set(id, color);
|
|
90
|
+
colors = next;
|
|
90
91
|
}
|
|
91
92
|
const firstUse = new Map();
|
|
92
93
|
collectFirstUseKeys(value, colors, firstUse);
|
|
@@ -118,29 +119,34 @@ function collectDeclarations(value, result) {
|
|
|
118
119
|
for (const child of value.anyOf)
|
|
119
120
|
collectDeclarations(child, result);
|
|
120
121
|
}
|
|
121
|
-
function collectUses(value,
|
|
122
|
+
function collectUses(value, result) {
|
|
122
123
|
if ('sameNode' in value) {
|
|
123
124
|
const { left, right } = value.sameNode;
|
|
124
|
-
if (left.kind === 'variable'
|
|
125
|
-
result.push(
|
|
126
|
-
if (right.kind === 'variable'
|
|
127
|
-
result.push(
|
|
125
|
+
if (left.kind === 'variable')
|
|
126
|
+
result.get(left.id)?.push(['same:', right]);
|
|
127
|
+
if (right.kind === 'variable')
|
|
128
|
+
result.get(right.id)?.push(['same:', left]);
|
|
128
129
|
return;
|
|
129
130
|
}
|
|
130
131
|
if (isEdge(value)) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (value.target.kind === 'variable'
|
|
135
|
-
result.push(`t:${
|
|
136
|
-
}
|
|
132
|
+
const edge = definitionRefIndex(value.class);
|
|
133
|
+
if (value.source.kind === 'variable')
|
|
134
|
+
result.get(value.source.id)?.push([`s:${edge}:`, value.target]);
|
|
135
|
+
if (value.target.kind === 'variable')
|
|
136
|
+
result.get(value.target.id)?.push([`t:${edge}:`, value.source]);
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
if ('exists' in value)
|
|
140
|
-
return collectUses(value.exists.where,
|
|
140
|
+
return collectUses(value.exists.where, result);
|
|
141
141
|
const children = 'allOf' in value ? value.allOf : value.anyOf;
|
|
142
142
|
for (const child of children)
|
|
143
|
-
collectUses(child,
|
|
143
|
+
collectUses(child, result);
|
|
144
|
+
}
|
|
145
|
+
function rank(names) {
|
|
146
|
+
const distinct = [...new Set(names.values())].sort(compareUtf8Bytes);
|
|
147
|
+
const width = String(distinct.length).length;
|
|
148
|
+
const ranks = new Map(distinct.map((name, index) => [name, String(index).padStart(width, '0')]));
|
|
149
|
+
return new Map([...names].map(([id, name]) => [id, ranks.get(name) ?? '']));
|
|
144
150
|
}
|
|
145
151
|
function termColor(term, colors) {
|
|
146
152
|
if (term.kind === 'variable')
|
|
@@ -181,8 +187,10 @@ function collectFirstUseKeys(value, colors, result, prefix = '$') {
|
|
|
181
187
|
}
|
|
182
188
|
const key = 'allOf' in value ? 'allOf' : 'anyOf';
|
|
183
189
|
const operands = 'allOf' in value ? value.allOf : value.anyOf;
|
|
184
|
-
const children =
|
|
185
|
-
|
|
190
|
+
const children = operands
|
|
191
|
+
.map((child) => [patternSignature(child, colors), child])
|
|
192
|
+
.sort(([left], [right]) => compareUtf8Bytes(left, right));
|
|
193
|
+
children.forEach(([, child], index) => collectFirstUseKeys(child, colors, result, `${prefix}:${key}:${index}`));
|
|
186
194
|
}
|
|
187
195
|
function patternSignature(value, colors) {
|
|
188
196
|
if ('sameNode' in value)
|
|
@@ -255,15 +263,24 @@ function uniqueJson(values) {
|
|
|
255
263
|
.sort(([left], [right]) => compareUtf8Bytes(left, right))
|
|
256
264
|
.map(([, value]) => value);
|
|
257
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* UTF-8 byte order without encoding: it is code-point order, which UTF-16 code units already follow
|
|
268
|
+
* except that surrogates (U+10000 and above) sort after U+E000–U+FFFF.
|
|
269
|
+
*/
|
|
258
270
|
function compareUtf8Bytes(left, right) {
|
|
259
|
-
const
|
|
260
|
-
const b = textEncoder.encode(right);
|
|
261
|
-
const length = Math.min(a.length, b.length);
|
|
271
|
+
const length = Math.min(left.length, right.length);
|
|
262
272
|
for (let index = 0; index < length; index++) {
|
|
263
|
-
|
|
264
|
-
|
|
273
|
+
const a = left.charCodeAt(index);
|
|
274
|
+
const b = right.charCodeAt(index);
|
|
275
|
+
if (a !== b)
|
|
276
|
+
return codePointOrder(a) - codePointOrder(b);
|
|
265
277
|
}
|
|
266
|
-
return
|
|
278
|
+
return left.length - right.length;
|
|
279
|
+
}
|
|
280
|
+
function codePointOrder(unit) {
|
|
281
|
+
if (unit >= 0xd800 && unit <= 0xdfff)
|
|
282
|
+
return unit + 0x2000;
|
|
283
|
+
return unit >= 0xe000 ? unit - 0x800 : unit;
|
|
267
284
|
}
|
|
268
285
|
function isEdge(value) {
|
|
269
286
|
return 'source' in value;
|