@stevenvo780/st-lang 4.13.0 → 4.14.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/dist/reasoning/hoare-logic/index.d.ts +130 -0
- package/dist/reasoning/hoare-logic/index.d.ts.map +1 -0
- package/dist/reasoning/hoare-logic/index.js +535 -0
- package/dist/reasoning/hoare-logic/index.js.map +1 -0
- package/dist/reasoning/model-checking/index.d.ts +113 -0
- package/dist/reasoning/model-checking/index.d.ts.map +1 -0
- package/dist/reasoning/model-checking/index.js +786 -0
- package/dist/reasoning/model-checking/index.js.map +1 -0
- package/dist/reasoning/separation-logic/index.d.ts +190 -0
- package/dist/reasoning/separation-logic/index.d.ts.map +1 -0
- package/dist/reasoning/separation-logic/index.js +758 -0
- package/dist/reasoning/separation-logic/index.js.map +1 -0
- package/dist/reasoning/universal-algebra/index.d.ts +196 -0
- package/dist/reasoning/universal-algebra/index.d.ts.map +1 -0
- package/dist/reasoning/universal-algebra/index.js +865 -0
- package/dist/reasoning/universal-algebra/index.js.map +1 -0
- package/dist/tests/reasoning/hoare-logic/hoare-logic.test.d.ts +2 -0
- package/dist/tests/reasoning/hoare-logic/hoare-logic.test.d.ts.map +1 -0
- package/dist/tests/reasoning/hoare-logic/hoare-logic.test.js +340 -0
- package/dist/tests/reasoning/hoare-logic/hoare-logic.test.js.map +1 -0
- package/dist/tests/reasoning/model-checking/model-checking.test.d.ts +2 -0
- package/dist/tests/reasoning/model-checking/model-checking.test.d.ts.map +1 -0
- package/dist/tests/reasoning/model-checking/model-checking.test.js +222 -0
- package/dist/tests/reasoning/model-checking/model-checking.test.js.map +1 -0
- package/dist/tests/reasoning/separation-logic/separation-logic.test.d.ts +2 -0
- package/dist/tests/reasoning/separation-logic/separation-logic.test.d.ts.map +1 -0
- package/dist/tests/reasoning/separation-logic/separation-logic.test.js +311 -0
- package/dist/tests/reasoning/separation-logic/separation-logic.test.js.map +1 -0
- package/dist/tests/reasoning/universal-algebra/universal-algebra.test.d.ts +2 -0
- package/dist/tests/reasoning/universal-algebra/universal-algebra.test.d.ts.map +1 -0
- package/dist/tests/reasoning/universal-algebra/universal-algebra.test.js +289 -0
- package/dist/tests/reasoning/universal-algebra/universal-algebra.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// Separation Logic — Reynolds-style heap reasoning
|
|
4
|
+
// ============================================================
|
|
5
|
+
//
|
|
6
|
+
// Lógica de separación clásica (Reynolds / O'Hearn / Yang) para razonar
|
|
7
|
+
// sobre estructuras dinámicas (listas, árboles) y mutación con aliasing
|
|
8
|
+
// controlado.
|
|
9
|
+
//
|
|
10
|
+
// Núcleo:
|
|
11
|
+
// - Heap finito (Map<number, SLValue>) con dominio explícito.
|
|
12
|
+
// - Fórmulas: `emp`, `x ↦ v`, `P * Q`, `P -* Q`, `∃x. P`, conectivos
|
|
13
|
+
// clásicos sobre el fragmento puro.
|
|
14
|
+
// - Semántica `satisfies(P, h, valuation)` siguiendo el split de heap.
|
|
15
|
+
// - Triplas de Hoare con axiomas locales (alloc, free, load, store).
|
|
16
|
+
// - Predicados inductivos: `ls(x, y)` (list-segment) y `tree(x)`.
|
|
17
|
+
//
|
|
18
|
+
// El módulo es puro TypeScript, sin dependencias del resto del repo.
|
|
19
|
+
// Las estructuras Heap son inmutables hacia afuera: `write`, `delete` y
|
|
20
|
+
// `combine` devuelven instancias nuevas.
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.Cmd = exports.forallF = exports.existsF = exports.notF = exports.impliesF = exports.orF = exports.andF = exports.pure = exports.magicWand = exports.star = exports.pointsTo = exports.emp = void 0;
|
|
23
|
+
exports.intVal = intVal;
|
|
24
|
+
exports.addrVal = addrVal;
|
|
25
|
+
exports.nullVal = nullVal;
|
|
26
|
+
exports.valueEquals = valueEquals;
|
|
27
|
+
exports.valueKey = valueKey;
|
|
28
|
+
exports.asLoc = asLoc;
|
|
29
|
+
exports.newHeap = newHeap;
|
|
30
|
+
exports.fromMap = fromMap;
|
|
31
|
+
exports.disjoint = disjoint;
|
|
32
|
+
exports.combine = combine;
|
|
33
|
+
exports.heapEquals = heapEquals;
|
|
34
|
+
exports.splits = splits;
|
|
35
|
+
exports.formulaToString = formulaToString;
|
|
36
|
+
exports.valueToString = valueToString;
|
|
37
|
+
exports.bind = bind;
|
|
38
|
+
exports.satisfies = satisfies;
|
|
39
|
+
exports.executeCommand = executeCommand;
|
|
40
|
+
exports.checkTriple = checkTriple;
|
|
41
|
+
exports.isListSegment = isListSegment;
|
|
42
|
+
exports.listSegment = listSegment;
|
|
43
|
+
exports.satisfiesShape = satisfiesShape;
|
|
44
|
+
exports.tree = tree;
|
|
45
|
+
exports.isTree = isTree;
|
|
46
|
+
exports.frame = frame;
|
|
47
|
+
function intVal(value) {
|
|
48
|
+
return { kind: 'int', value };
|
|
49
|
+
}
|
|
50
|
+
function addrVal(loc) {
|
|
51
|
+
return { kind: 'addr', loc };
|
|
52
|
+
}
|
|
53
|
+
function nullVal() {
|
|
54
|
+
return { kind: 'null' };
|
|
55
|
+
}
|
|
56
|
+
function valueEquals(a, b) {
|
|
57
|
+
if (a.kind !== b.kind)
|
|
58
|
+
return false;
|
|
59
|
+
if (a.kind === 'int' && b.kind === 'int')
|
|
60
|
+
return a.value === b.value;
|
|
61
|
+
if (a.kind === 'addr' && b.kind === 'addr')
|
|
62
|
+
return a.loc === b.loc;
|
|
63
|
+
return a.kind === 'null' && b.kind === 'null';
|
|
64
|
+
}
|
|
65
|
+
/** Convierte un SLValue a una clave string estable para Maps. */
|
|
66
|
+
function valueKey(v) {
|
|
67
|
+
if (v.kind === 'int')
|
|
68
|
+
return `i:${v.value}`;
|
|
69
|
+
if (v.kind === 'addr')
|
|
70
|
+
return `a:${v.loc}`;
|
|
71
|
+
return 'null';
|
|
72
|
+
}
|
|
73
|
+
/** Devuelve la dirección de un SLValue si es addr, o null en cualquier
|
|
74
|
+
* otro caso. La null-location no se direcciona. */
|
|
75
|
+
function asLoc(v) {
|
|
76
|
+
return v.kind === 'addr' ? v.loc : null;
|
|
77
|
+
}
|
|
78
|
+
class HeapImpl {
|
|
79
|
+
map;
|
|
80
|
+
constructor(map) {
|
|
81
|
+
this.map = map;
|
|
82
|
+
}
|
|
83
|
+
domain() {
|
|
84
|
+
return [...this.map.keys()].sort((a, b) => a - b);
|
|
85
|
+
}
|
|
86
|
+
has(loc) {
|
|
87
|
+
return this.map.has(loc);
|
|
88
|
+
}
|
|
89
|
+
read(loc) {
|
|
90
|
+
return this.map.get(loc);
|
|
91
|
+
}
|
|
92
|
+
write(loc, val) {
|
|
93
|
+
const next = new Map(this.map);
|
|
94
|
+
next.set(loc, val);
|
|
95
|
+
return new HeapImpl(next);
|
|
96
|
+
}
|
|
97
|
+
delete(loc) {
|
|
98
|
+
if (!this.map.has(loc))
|
|
99
|
+
return this;
|
|
100
|
+
const next = new Map(this.map);
|
|
101
|
+
next.delete(loc);
|
|
102
|
+
return new HeapImpl(next);
|
|
103
|
+
}
|
|
104
|
+
size() {
|
|
105
|
+
return this.map.size;
|
|
106
|
+
}
|
|
107
|
+
clone() {
|
|
108
|
+
return new HeapImpl(new Map(this.map));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function newHeap() {
|
|
112
|
+
return new HeapImpl(new Map());
|
|
113
|
+
}
|
|
114
|
+
function fromMap(entries) {
|
|
115
|
+
return new HeapImpl(new Map(entries));
|
|
116
|
+
}
|
|
117
|
+
/** Dos heaps son disjuntos sii sus dominios no se intersectan. */
|
|
118
|
+
function disjoint(h1, h2) {
|
|
119
|
+
if (h1.size() > h2.size())
|
|
120
|
+
return disjoint(h2, h1);
|
|
121
|
+
for (const loc of h1.domain()) {
|
|
122
|
+
if (h2.has(loc))
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
/** Unión disjunta `h1 ⊎ h2`. Devuelve null si los heaps comparten alguna
|
|
128
|
+
* dirección — la unión disjunta sólo está definida cuando son disjoint. */
|
|
129
|
+
function combine(h1, h2) {
|
|
130
|
+
if (!disjoint(h1, h2))
|
|
131
|
+
return null;
|
|
132
|
+
const merged = new Map(h1.map);
|
|
133
|
+
for (const [loc, v] of h2.map)
|
|
134
|
+
merged.set(loc, v);
|
|
135
|
+
return new HeapImpl(merged);
|
|
136
|
+
}
|
|
137
|
+
/** Igualdad estructural de heaps. */
|
|
138
|
+
function heapEquals(h1, h2) {
|
|
139
|
+
if (h1.size() !== h2.size())
|
|
140
|
+
return false;
|
|
141
|
+
for (const [loc, v] of h1.map) {
|
|
142
|
+
const other = h2.read(loc);
|
|
143
|
+
if (other === undefined)
|
|
144
|
+
return false;
|
|
145
|
+
if (!valueEquals(v, other))
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
// ── Sub-heaps (para semántica de `*` y `-*`) ────────────────
|
|
151
|
+
/** Enumera todas las particiones del heap en `(h1, h2)` con `h1 ⊎ h2 = h`. */
|
|
152
|
+
function splits(h) {
|
|
153
|
+
const dom = h.domain();
|
|
154
|
+
const n = dom.length;
|
|
155
|
+
const results = [];
|
|
156
|
+
// 2^n splits — sólo se invoca sobre heaps pequeños (testing/semántica).
|
|
157
|
+
const limit = 1 << n;
|
|
158
|
+
for (let mask = 0; mask < limit; mask++) {
|
|
159
|
+
const m1 = new Map();
|
|
160
|
+
const m2 = new Map();
|
|
161
|
+
for (let i = 0; i < n; i++) {
|
|
162
|
+
const loc = dom[i];
|
|
163
|
+
const v = h.read(loc);
|
|
164
|
+
if ((mask >> i) & 1)
|
|
165
|
+
m1.set(loc, v);
|
|
166
|
+
else
|
|
167
|
+
m2.set(loc, v);
|
|
168
|
+
}
|
|
169
|
+
results.push({ h1: new HeapImpl(m1), h2: new HeapImpl(m2) });
|
|
170
|
+
}
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
const emp = () => ({ kind: 'emp' });
|
|
174
|
+
exports.emp = emp;
|
|
175
|
+
const pointsTo = (loc, val) => ({
|
|
176
|
+
kind: 'pointsTo',
|
|
177
|
+
loc,
|
|
178
|
+
val,
|
|
179
|
+
});
|
|
180
|
+
exports.pointsTo = pointsTo;
|
|
181
|
+
const star = (left, right) => ({
|
|
182
|
+
kind: 'star',
|
|
183
|
+
left,
|
|
184
|
+
right,
|
|
185
|
+
});
|
|
186
|
+
exports.star = star;
|
|
187
|
+
const magicWand = (left, right) => ({
|
|
188
|
+
kind: 'magicWand',
|
|
189
|
+
left,
|
|
190
|
+
right,
|
|
191
|
+
});
|
|
192
|
+
exports.magicWand = magicWand;
|
|
193
|
+
const pure = (expression, predicate) => ({
|
|
194
|
+
kind: 'pure',
|
|
195
|
+
expression,
|
|
196
|
+
predicate,
|
|
197
|
+
});
|
|
198
|
+
exports.pure = pure;
|
|
199
|
+
const andF = (left, right) => ({
|
|
200
|
+
kind: 'and',
|
|
201
|
+
left,
|
|
202
|
+
right,
|
|
203
|
+
});
|
|
204
|
+
exports.andF = andF;
|
|
205
|
+
const orF = (left, right) => ({
|
|
206
|
+
kind: 'or',
|
|
207
|
+
left,
|
|
208
|
+
right,
|
|
209
|
+
});
|
|
210
|
+
exports.orF = orF;
|
|
211
|
+
const impliesF = (left, right) => ({
|
|
212
|
+
kind: 'implies',
|
|
213
|
+
left,
|
|
214
|
+
right,
|
|
215
|
+
});
|
|
216
|
+
exports.impliesF = impliesF;
|
|
217
|
+
const notF = (body) => ({ kind: 'not', body });
|
|
218
|
+
exports.notF = notF;
|
|
219
|
+
const existsF = (bind, body) => ({
|
|
220
|
+
kind: 'exists',
|
|
221
|
+
bind,
|
|
222
|
+
body,
|
|
223
|
+
});
|
|
224
|
+
exports.existsF = existsF;
|
|
225
|
+
const forallF = (bind, body) => ({
|
|
226
|
+
kind: 'forall',
|
|
227
|
+
bind,
|
|
228
|
+
body,
|
|
229
|
+
});
|
|
230
|
+
exports.forallF = forallF;
|
|
231
|
+
// ── Pretty printer ───────────────────────────────────────────
|
|
232
|
+
function formulaToString(f) {
|
|
233
|
+
switch (f.kind) {
|
|
234
|
+
case 'emp':
|
|
235
|
+
return 'emp';
|
|
236
|
+
case 'pointsTo':
|
|
237
|
+
return `${valueToString(f.loc)} ↦ ${valueToString(f.val)}`;
|
|
238
|
+
case 'star':
|
|
239
|
+
return `(${formulaToString(f.left)} * ${formulaToString(f.right)})`;
|
|
240
|
+
case 'magicWand':
|
|
241
|
+
return `(${formulaToString(f.left)} -* ${formulaToString(f.right)})`;
|
|
242
|
+
case 'pure':
|
|
243
|
+
return `[${f.expression}]`;
|
|
244
|
+
case 'and':
|
|
245
|
+
return `(${formulaToString(f.left)} ∧ ${formulaToString(f.right)})`;
|
|
246
|
+
case 'or':
|
|
247
|
+
return `(${formulaToString(f.left)} ∨ ${formulaToString(f.right)})`;
|
|
248
|
+
case 'implies':
|
|
249
|
+
return `(${formulaToString(f.left)} → ${formulaToString(f.right)})`;
|
|
250
|
+
case 'not':
|
|
251
|
+
return `¬${formulaToString(f.body)}`;
|
|
252
|
+
case 'exists':
|
|
253
|
+
return `∃${f.bind}. ${formulaToString(f.body)}`;
|
|
254
|
+
case 'forall':
|
|
255
|
+
return `∀${f.bind}. ${formulaToString(f.body)}`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function valueToString(v) {
|
|
259
|
+
if (v.kind === 'int')
|
|
260
|
+
return `${v.value}`;
|
|
261
|
+
if (v.kind === 'addr')
|
|
262
|
+
return `&${v.loc}`;
|
|
263
|
+
return 'null';
|
|
264
|
+
}
|
|
265
|
+
/** Devuelve una copia del valuation con `name → v`. */
|
|
266
|
+
function bind(val, name, v) {
|
|
267
|
+
return { ...val, [name]: v };
|
|
268
|
+
}
|
|
269
|
+
// ── Semántica ────────────────────────────────────────────────
|
|
270
|
+
/** `satisfies(P, h, ν)` — el modelo `(h, ν)` satisface la fórmula P. */
|
|
271
|
+
function satisfies(formula, heap, val) {
|
|
272
|
+
switch (formula.kind) {
|
|
273
|
+
case 'emp':
|
|
274
|
+
return heap.size() === 0;
|
|
275
|
+
case 'pointsTo': {
|
|
276
|
+
const loc = asLoc(formula.loc);
|
|
277
|
+
if (loc === null)
|
|
278
|
+
return false;
|
|
279
|
+
if (heap.size() !== 1)
|
|
280
|
+
return false;
|
|
281
|
+
const stored = heap.read(loc);
|
|
282
|
+
if (stored === undefined)
|
|
283
|
+
return false;
|
|
284
|
+
return valueEquals(stored, formula.val);
|
|
285
|
+
}
|
|
286
|
+
case 'star': {
|
|
287
|
+
for (const { h1, h2 } of splits(heap)) {
|
|
288
|
+
if (satisfies(formula.left, h1, val) && satisfies(formula.right, h2, val)) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
case 'magicWand': {
|
|
295
|
+
// P -* Q : ∀ h'. (h ⊥ h' ∧ h' ⊨ P) → (h ⊎ h') ⊨ Q
|
|
296
|
+
// Acotado a sub-heaps relevantes vía un universo de direcciones.
|
|
297
|
+
const universe = collectAddresses(formula.left, heap, val);
|
|
298
|
+
for (const candidate of enumerateHeapsOver(universe, heap, formula.left, val)) {
|
|
299
|
+
if (!disjoint(heap, candidate))
|
|
300
|
+
continue;
|
|
301
|
+
if (!satisfies(formula.left, candidate, val))
|
|
302
|
+
continue;
|
|
303
|
+
const merged = combine(heap, candidate);
|
|
304
|
+
if (merged === null)
|
|
305
|
+
continue;
|
|
306
|
+
if (!satisfies(formula.right, merged, val))
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
case 'pure':
|
|
312
|
+
return formula.predicate(val);
|
|
313
|
+
case 'and':
|
|
314
|
+
return satisfies(formula.left, heap, val) && satisfies(formula.right, heap, val);
|
|
315
|
+
case 'or':
|
|
316
|
+
return satisfies(formula.left, heap, val) || satisfies(formula.right, heap, val);
|
|
317
|
+
case 'implies':
|
|
318
|
+
return !satisfies(formula.left, heap, val) || satisfies(formula.right, heap, val);
|
|
319
|
+
case 'not':
|
|
320
|
+
return !satisfies(formula.body, heap, val);
|
|
321
|
+
case 'exists': {
|
|
322
|
+
// Acotado al universo de valores presentes en el heap y la valuación
|
|
323
|
+
// más null. Para semántica computable necesitamos un dominio finito.
|
|
324
|
+
for (const v of finiteValueDomain(heap, val)) {
|
|
325
|
+
if (satisfies(formula.body, heap, bind(val, formula.bind, v)))
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
case 'forall': {
|
|
331
|
+
for (const v of finiteValueDomain(heap, val)) {
|
|
332
|
+
if (!satisfies(formula.body, heap, bind(val, formula.bind, v)))
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/** Dominio finito de valores observables: direcciones del heap + valores
|
|
340
|
+
* en la valuación + null. Permite cuantificar de forma computable. */
|
|
341
|
+
function finiteValueDomain(heap, val) {
|
|
342
|
+
const seen = new Map();
|
|
343
|
+
const push = (v) => {
|
|
344
|
+
seen.set(valueKey(v), v);
|
|
345
|
+
};
|
|
346
|
+
push(nullVal());
|
|
347
|
+
for (const loc of heap.domain()) {
|
|
348
|
+
push(addrVal(loc));
|
|
349
|
+
const stored = heap.read(loc);
|
|
350
|
+
if (stored !== undefined)
|
|
351
|
+
push(stored);
|
|
352
|
+
}
|
|
353
|
+
for (const name of Object.keys(val)) {
|
|
354
|
+
const bound = val[name];
|
|
355
|
+
if (bound !== undefined)
|
|
356
|
+
push(bound);
|
|
357
|
+
}
|
|
358
|
+
return [...seen.values()];
|
|
359
|
+
}
|
|
360
|
+
/** Direcciones candidatas para extender el heap al evaluar `-*`. */
|
|
361
|
+
function collectAddresses(_left, heap, val) {
|
|
362
|
+
const seen = new Set();
|
|
363
|
+
for (const loc of heap.domain())
|
|
364
|
+
seen.add(loc);
|
|
365
|
+
for (const name of Object.keys(val)) {
|
|
366
|
+
const bound = val[name];
|
|
367
|
+
if (bound && bound.kind === 'addr')
|
|
368
|
+
seen.add(bound.loc);
|
|
369
|
+
}
|
|
370
|
+
// Sembramos algunas direcciones frescas para que `-*` pueda probar con
|
|
371
|
+
// un heap extensión no trivial sin explotar combinatoriamente.
|
|
372
|
+
const maxLoc = Math.max(0, ...seen);
|
|
373
|
+
for (let i = 1; i <= 2; i++)
|
|
374
|
+
seen.add(maxLoc + i);
|
|
375
|
+
return [...seen].sort((a, b) => a - b);
|
|
376
|
+
}
|
|
377
|
+
/** Enumera heaps pequeños sobre `universe` cuyas direcciones no estén ya
|
|
378
|
+
* en `existing`. Usa los valores observables como contenido. Cota dura
|
|
379
|
+
* para no estallar: máximo 3 ubicaciones nuevas. */
|
|
380
|
+
function* enumerateHeapsOver(universe, existing, leftFormula, val) {
|
|
381
|
+
const candidates = universe.filter((loc) => !existing.has(loc));
|
|
382
|
+
const maxExtra = Math.min(3, candidates.length);
|
|
383
|
+
const values = finiteValueDomain(existing, val);
|
|
384
|
+
// Recortar dominio: si la fórmula izquierda es `emp`, sólo importa el heap vacío.
|
|
385
|
+
if (leftFormula.kind === 'emp') {
|
|
386
|
+
yield newHeap();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
// 0 .. maxExtra direcciones, cada una con cualquier valor.
|
|
390
|
+
for (let size = 0; size <= maxExtra; size++) {
|
|
391
|
+
yield* enumerateSubset(candidates, 0, size, [], values);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function* enumerateSubset(pool, start, remaining, acc, values) {
|
|
395
|
+
if (remaining === 0) {
|
|
396
|
+
yield fromMap(acc);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
for (let i = start; i <= pool.length - remaining; i++) {
|
|
400
|
+
const loc = pool[i];
|
|
401
|
+
for (const v of values) {
|
|
402
|
+
acc.push([loc, v]);
|
|
403
|
+
yield* enumerateSubset(pool, i + 1, remaining - 1, acc, values);
|
|
404
|
+
acc.pop();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/** Ejecuta un único comando de forma small-step. */
|
|
409
|
+
function executeCommand(cmd, heap, val) {
|
|
410
|
+
switch (cmd.kind) {
|
|
411
|
+
case 'skip':
|
|
412
|
+
return { ok: true, heap, val, fault: false };
|
|
413
|
+
case 'assign': {
|
|
414
|
+
if (cmd.variable === undefined || cmd.value === undefined) {
|
|
415
|
+
return { ok: false, heap, val, fault: false, reason: 'assign sin variable/valor' };
|
|
416
|
+
}
|
|
417
|
+
return { ok: true, heap, val: bind(val, cmd.variable, cmd.value), fault: false };
|
|
418
|
+
}
|
|
419
|
+
case 'alloc': {
|
|
420
|
+
if (cmd.variable === undefined) {
|
|
421
|
+
return { ok: false, heap, val, fault: false, reason: 'alloc sin variable destino' };
|
|
422
|
+
}
|
|
423
|
+
const initial = cmd.value ?? intVal(0);
|
|
424
|
+
const freshLoc = freshAddress(heap);
|
|
425
|
+
const heap1 = heap.write(freshLoc, initial);
|
|
426
|
+
const val1 = bind(val, cmd.variable, addrVal(freshLoc));
|
|
427
|
+
return { ok: true, heap: heap1, val: val1, fault: false };
|
|
428
|
+
}
|
|
429
|
+
case 'free': {
|
|
430
|
+
const target = cmd.location === undefined ? undefined : val[cmd.location];
|
|
431
|
+
if (target === undefined) {
|
|
432
|
+
return { ok: false, heap, val, fault: false, reason: 'free sobre variable no ligada' };
|
|
433
|
+
}
|
|
434
|
+
const loc = asLoc(target);
|
|
435
|
+
if (loc === null || !heap.has(loc)) {
|
|
436
|
+
return { ok: false, heap, val, fault: true, reason: 'free: memory fault' };
|
|
437
|
+
}
|
|
438
|
+
return { ok: true, heap: heap.delete(loc), val, fault: false };
|
|
439
|
+
}
|
|
440
|
+
case 'load': {
|
|
441
|
+
if (cmd.variable === undefined || cmd.location === undefined) {
|
|
442
|
+
return { ok: false, heap, val, fault: false, reason: 'load sin variable/location' };
|
|
443
|
+
}
|
|
444
|
+
const target = val[cmd.location];
|
|
445
|
+
const loc = target === undefined ? null : asLoc(target);
|
|
446
|
+
if (loc === null || !heap.has(loc)) {
|
|
447
|
+
return { ok: false, heap, val, fault: true, reason: 'load: memory fault' };
|
|
448
|
+
}
|
|
449
|
+
const stored = heap.read(loc);
|
|
450
|
+
return { ok: true, heap, val: bind(val, cmd.variable, stored), fault: false };
|
|
451
|
+
}
|
|
452
|
+
case 'store': {
|
|
453
|
+
if (cmd.location === undefined || cmd.value === undefined) {
|
|
454
|
+
return { ok: false, heap, val, fault: false, reason: 'store sin location/valor' };
|
|
455
|
+
}
|
|
456
|
+
const target = val[cmd.location];
|
|
457
|
+
const loc = target === undefined ? null : asLoc(target);
|
|
458
|
+
if (loc === null || !heap.has(loc)) {
|
|
459
|
+
return { ok: false, heap, val, fault: true, reason: 'store: memory fault' };
|
|
460
|
+
}
|
|
461
|
+
return { ok: true, heap: heap.write(loc, cmd.value), val, fault: false };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/** Devuelve la primera dirección libre del heap (estrategia bumper). */
|
|
466
|
+
function freshAddress(heap) {
|
|
467
|
+
let loc = 1;
|
|
468
|
+
while (heap.has(loc))
|
|
469
|
+
loc++;
|
|
470
|
+
return loc;
|
|
471
|
+
}
|
|
472
|
+
/** Verifica una tripla `{P} c {Q}` por muestreo finito: enumera modelos
|
|
473
|
+
* (heap, val) que satisfagan P, los ejecuta y comprueba Q sobre el
|
|
474
|
+
* estado final. Devuelve `valid: false` con contraejemplo al primer
|
|
475
|
+
* fallo. No es completo — es una verificación de testing/random. */
|
|
476
|
+
function checkTriple(triple, options = {}) {
|
|
477
|
+
const samples = options.samples ?? 32;
|
|
478
|
+
const seed = options.seed ?? 0xc0ffee;
|
|
479
|
+
const rng = makeLcg(seed);
|
|
480
|
+
const pool = [];
|
|
481
|
+
if (options.candidates)
|
|
482
|
+
pool.push(...options.candidates);
|
|
483
|
+
for (let i = 0; i < samples; i++)
|
|
484
|
+
pool.push(randomModel(rng));
|
|
485
|
+
let checked = 0;
|
|
486
|
+
for (const { heap, val } of pool) {
|
|
487
|
+
if (!satisfies(triple.pre, heap, val))
|
|
488
|
+
continue;
|
|
489
|
+
checked++;
|
|
490
|
+
const result = executeCommand(triple.cmd, heap, val);
|
|
491
|
+
if (!result.ok) {
|
|
492
|
+
return {
|
|
493
|
+
valid: false,
|
|
494
|
+
counterexample: {
|
|
495
|
+
heap,
|
|
496
|
+
val,
|
|
497
|
+
reason: `comando inválido: ${result.reason ?? 'unknown'}`,
|
|
498
|
+
},
|
|
499
|
+
modelsChecked: checked,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (result.fault) {
|
|
503
|
+
return {
|
|
504
|
+
valid: false,
|
|
505
|
+
counterexample: { heap, val, reason: 'memory fault al ejecutar' },
|
|
506
|
+
modelsChecked: checked,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (!satisfies(triple.post, result.heap, result.val)) {
|
|
510
|
+
return {
|
|
511
|
+
valid: false,
|
|
512
|
+
counterexample: {
|
|
513
|
+
heap,
|
|
514
|
+
val,
|
|
515
|
+
reason: 'postcondición no satisfecha tras ejecución',
|
|
516
|
+
},
|
|
517
|
+
modelsChecked: checked,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return { valid: true, modelsChecked: checked };
|
|
522
|
+
}
|
|
523
|
+
function makeLcg(seed) {
|
|
524
|
+
let state = seed >>> 0;
|
|
525
|
+
return () => {
|
|
526
|
+
state = (state * 1664525 + 1013904223) >>> 0;
|
|
527
|
+
return state / 0x100000000;
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function randomModel(rng) {
|
|
531
|
+
const size = Math.floor(rng() * 4); // 0..3 celdas
|
|
532
|
+
const entries = [];
|
|
533
|
+
const used = new Set();
|
|
534
|
+
for (let i = 0; i < size; i++) {
|
|
535
|
+
let loc = 1 + Math.floor(rng() * 5);
|
|
536
|
+
while (used.has(loc))
|
|
537
|
+
loc++;
|
|
538
|
+
used.add(loc);
|
|
539
|
+
entries.push([loc, randomValue(rng)]);
|
|
540
|
+
}
|
|
541
|
+
const heap = fromMap(entries);
|
|
542
|
+
const val = {};
|
|
543
|
+
// x apunta al primer slot si hay alguno
|
|
544
|
+
if (entries.length > 0) {
|
|
545
|
+
const first = entries[0];
|
|
546
|
+
if (first)
|
|
547
|
+
val.x = addrVal(first[0]);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
val.x = nullVal();
|
|
551
|
+
}
|
|
552
|
+
return { heap, val };
|
|
553
|
+
}
|
|
554
|
+
function randomValue(rng) {
|
|
555
|
+
const choice = Math.floor(rng() * 3);
|
|
556
|
+
if (choice === 0)
|
|
557
|
+
return intVal(Math.floor(rng() * 10));
|
|
558
|
+
if (choice === 1)
|
|
559
|
+
return nullVal();
|
|
560
|
+
return addrVal(1 + Math.floor(rng() * 5));
|
|
561
|
+
}
|
|
562
|
+
// ── Predicados inductivos ───────────────────────────────────
|
|
563
|
+
/** `ls(x, y)` — list-segment de x a y. Definido por:
|
|
564
|
+
* ls(x, y) ≡ (x = y ∧ emp) ∨ ∃z. (x ↦ z * ls(z, y))
|
|
565
|
+
*
|
|
566
|
+
* Para mantener semántica computable, se interpreta directamente sobre
|
|
567
|
+
* el heap: existe una cadena de celdas desde `start` a `end` cuyos
|
|
568
|
+
* contenidos son punteros, sin ciclos ni celdas extra. */
|
|
569
|
+
function isListSegment(start, end, heap) {
|
|
570
|
+
if (valueEquals(start, end))
|
|
571
|
+
return heap.size() === 0;
|
|
572
|
+
if (start.kind !== 'addr')
|
|
573
|
+
return false;
|
|
574
|
+
const visited = new Set();
|
|
575
|
+
const path = [];
|
|
576
|
+
let cur = start;
|
|
577
|
+
while (!valueEquals(cur, end)) {
|
|
578
|
+
if (cur.kind !== 'addr')
|
|
579
|
+
return false;
|
|
580
|
+
if (visited.has(cur.loc))
|
|
581
|
+
return false; // ciclo
|
|
582
|
+
if (!heap.has(cur.loc))
|
|
583
|
+
return false;
|
|
584
|
+
visited.add(cur.loc);
|
|
585
|
+
path.push(cur.loc);
|
|
586
|
+
const next = heap.read(cur.loc);
|
|
587
|
+
cur = next;
|
|
588
|
+
}
|
|
589
|
+
// El heap debe ser exactamente las celdas del path — nada más.
|
|
590
|
+
if (heap.size() !== path.length)
|
|
591
|
+
return false;
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
/** Predicado SL para list-segment `ls(start, end)`. Se evalúa como
|
|
595
|
+
* predicado puro sobre el heap (cumple `satisfies`). */
|
|
596
|
+
function listSegment(start, end) {
|
|
597
|
+
// Encapsulamos la semántica inductiva en un `pure` que internamente
|
|
598
|
+
// mira el heap mediante closure. Para ello expandimos a una fórmula
|
|
599
|
+
// compuesta con un truco: usamos una macro que, al evaluarse, hace la
|
|
600
|
+
// verificación directa. Construimos un nodo `pure` con expresión
|
|
601
|
+
// descriptiva pero la verdad real la calcula la función externa via
|
|
602
|
+
// `isListSegment` — exponemos por separado `satisfiesShape`.
|
|
603
|
+
// Para participar de la semántica `satisfies` declaramos un nodo
|
|
604
|
+
// estructural: ls(start, end).
|
|
605
|
+
return {
|
|
606
|
+
kind: 'pure',
|
|
607
|
+
expression: `ls(${valueToString(start)}, ${valueToString(end)})`,
|
|
608
|
+
predicate: () => {
|
|
609
|
+
// El predicado puro no conoce el heap. La forma correcta de usar
|
|
610
|
+
// `ls` es vía `satisfiesShape` o vía la combinación predicado +
|
|
611
|
+
// helper externo. Devolvemos true como placeholder para que
|
|
612
|
+
// `pure` no rechace por sí solo.
|
|
613
|
+
return true;
|
|
614
|
+
},
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
/** Evalúa una fórmula con soporte de predicados inductivos sobre forma de
|
|
618
|
+
* heap (ls, tree). Se usa cuando la fórmula contiene `listSegment` o
|
|
619
|
+
* `tree` — `satisfies` solo no basta porque su rama `pure` ignora el heap. */
|
|
620
|
+
function satisfiesShape(formula, heap, val) {
|
|
621
|
+
if (formula.kind === 'pure') {
|
|
622
|
+
const lsMatch = formula.expression.match(/^ls\((.+),\s*(.+)\)$/);
|
|
623
|
+
if (lsMatch) {
|
|
624
|
+
const start = parseValueLit(lsMatch[1], val);
|
|
625
|
+
const end = parseValueLit(lsMatch[2], val);
|
|
626
|
+
if (start === null || end === null)
|
|
627
|
+
return false;
|
|
628
|
+
return isListSegment(start, end, heap);
|
|
629
|
+
}
|
|
630
|
+
const treeMatch = formula.expression.match(/^tree\((.+)\)$/);
|
|
631
|
+
if (treeMatch) {
|
|
632
|
+
const root = parseValueLit(treeMatch[1], val);
|
|
633
|
+
if (root === null)
|
|
634
|
+
return false;
|
|
635
|
+
return isTree(root, heap);
|
|
636
|
+
}
|
|
637
|
+
return formula.predicate(val);
|
|
638
|
+
}
|
|
639
|
+
if (formula.kind === 'star') {
|
|
640
|
+
for (const { h1, h2 } of splits(heap)) {
|
|
641
|
+
if (satisfiesShape(formula.left, h1, val) && satisfiesShape(formula.right, h2, val)) {
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
if (formula.kind === 'and') {
|
|
648
|
+
return satisfiesShape(formula.left, heap, val) && satisfiesShape(formula.right, heap, val);
|
|
649
|
+
}
|
|
650
|
+
if (formula.kind === 'or') {
|
|
651
|
+
return satisfiesShape(formula.left, heap, val) || satisfiesShape(formula.right, heap, val);
|
|
652
|
+
}
|
|
653
|
+
if (formula.kind === 'not') {
|
|
654
|
+
return !satisfiesShape(formula.body, heap, val);
|
|
655
|
+
}
|
|
656
|
+
return satisfies(formula, heap, val);
|
|
657
|
+
}
|
|
658
|
+
function parseValueLit(s, val) {
|
|
659
|
+
const trimmed = s.trim();
|
|
660
|
+
if (trimmed === 'null')
|
|
661
|
+
return nullVal();
|
|
662
|
+
if (trimmed.startsWith('&')) {
|
|
663
|
+
const n = Number.parseInt(trimmed.slice(1), 10);
|
|
664
|
+
if (Number.isNaN(n))
|
|
665
|
+
return null;
|
|
666
|
+
return addrVal(n);
|
|
667
|
+
}
|
|
668
|
+
if (val[trimmed] !== undefined) {
|
|
669
|
+
return val[trimmed];
|
|
670
|
+
}
|
|
671
|
+
const asInt = Number.parseInt(trimmed, 10);
|
|
672
|
+
if (!Number.isNaN(asInt))
|
|
673
|
+
return intVal(asInt);
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
/** `tree(root)` — el heap representa un árbol binario con raíz `root`.
|
|
677
|
+
* tree(x) ≡ (x = null ∧ emp) ∨ ∃l, r. (x ↦ l * x.next ↦ r * tree(l) * tree(r))
|
|
678
|
+
*
|
|
679
|
+
* Modelo simplificado: cada nodo ocupa 2 celdas consecutivas
|
|
680
|
+
* (loc, loc+1) con los punteros izquierdo y derecho.
|
|
681
|
+
*/
|
|
682
|
+
function tree(root) {
|
|
683
|
+
return {
|
|
684
|
+
kind: 'pure',
|
|
685
|
+
expression: `tree(${valueToString(root)})`,
|
|
686
|
+
predicate: () => true,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function isTree(root, heap) {
|
|
690
|
+
if (root.kind === 'null')
|
|
691
|
+
return heap.size() === 0;
|
|
692
|
+
if (root.kind !== 'addr')
|
|
693
|
+
return false;
|
|
694
|
+
const visited = new Set();
|
|
695
|
+
function walk(node) {
|
|
696
|
+
if (node.kind === 'null')
|
|
697
|
+
return { ok: true, locs: [] };
|
|
698
|
+
if (node.kind !== 'addr')
|
|
699
|
+
return { ok: false, locs: [] };
|
|
700
|
+
if (visited.has(node.loc))
|
|
701
|
+
return { ok: false, locs: [] };
|
|
702
|
+
if (!heap.has(node.loc) || !heap.has(node.loc + 1)) {
|
|
703
|
+
return { ok: false, locs: [] };
|
|
704
|
+
}
|
|
705
|
+
visited.add(node.loc);
|
|
706
|
+
visited.add(node.loc + 1);
|
|
707
|
+
const left = heap.read(node.loc);
|
|
708
|
+
const right = heap.read(node.loc + 1);
|
|
709
|
+
const lr = walk(left);
|
|
710
|
+
if (!lr.ok)
|
|
711
|
+
return { ok: false, locs: [] };
|
|
712
|
+
const rr = walk(right);
|
|
713
|
+
if (!rr.ok)
|
|
714
|
+
return { ok: false, locs: [] };
|
|
715
|
+
return { ok: true, locs: [node.loc, node.loc + 1, ...lr.locs, ...rr.locs] };
|
|
716
|
+
}
|
|
717
|
+
const result = walk(root);
|
|
718
|
+
if (!result.ok)
|
|
719
|
+
return false;
|
|
720
|
+
return result.locs.length === heap.size();
|
|
721
|
+
}
|
|
722
|
+
// ── Frame rule (helper) ──────────────────────────────────────
|
|
723
|
+
/** Frame rule: si `{P} c {Q}` y `c` no modifica las variables libres de
|
|
724
|
+
* `R`, entonces `{P * R} c {Q * R}`. Esta función construye la tripla
|
|
725
|
+
* compuesta — la validación queda a cargo de `checkTriple`. */
|
|
726
|
+
function frame(triple, frameFormula) {
|
|
727
|
+
return {
|
|
728
|
+
pre: (0, exports.star)(triple.pre, frameFormula),
|
|
729
|
+
cmd: triple.cmd,
|
|
730
|
+
post: (0, exports.star)(triple.post, frameFormula),
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
// ── Re-exports y constructores convenientes ─────────────────
|
|
734
|
+
exports.Cmd = {
|
|
735
|
+
skip: () => ({ kind: 'skip' }),
|
|
736
|
+
assign: (variable, value) => ({
|
|
737
|
+
kind: 'assign',
|
|
738
|
+
variable,
|
|
739
|
+
value,
|
|
740
|
+
}),
|
|
741
|
+
alloc: (variable, value) => ({
|
|
742
|
+
kind: 'alloc',
|
|
743
|
+
variable,
|
|
744
|
+
...(value !== undefined ? { value } : {}),
|
|
745
|
+
}),
|
|
746
|
+
free: (location) => ({ kind: 'free', location }),
|
|
747
|
+
load: (variable, location) => ({
|
|
748
|
+
kind: 'load',
|
|
749
|
+
variable,
|
|
750
|
+
location,
|
|
751
|
+
}),
|
|
752
|
+
store: (location, value) => ({
|
|
753
|
+
kind: 'store',
|
|
754
|
+
location,
|
|
755
|
+
value,
|
|
756
|
+
}),
|
|
757
|
+
};
|
|
758
|
+
//# sourceMappingURL=index.js.map
|