@bramas/simulant-lang 0.0.0-stage → 0.1.1
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/LICENCE.txt +515 -0
- package/dist/builder.js +221 -0
- package/dist/builtins.js +156 -0
- package/dist/check.js +540 -0
- package/dist/elab/elaborate.js +1094 -0
- package/dist/elab/lower.js +372 -0
- package/dist/elab/node.js +29 -0
- package/dist/elab/program.js +45 -0
- package/dist/ir.js +9 -0
- package/dist/layout.js +56 -0
- package/dist/parse/ast.js +1 -0
- package/dist/parse/lexer.js +151 -0
- package/dist/parse/parser.js +825 -0
- package/dist/resolve.js +31 -0
- package/dist/standalone.js +45 -0
- package/dist/types.js +109 -0
- package/package.json +27 -3
- package/src/builder.ts +358 -0
- package/src/builtins.ts +191 -0
- package/src/check.ts +508 -0
- package/src/elab/elaborate.ts +1045 -0
- package/src/elab/lower.ts +405 -0
- package/src/elab/node.ts +32 -0
- package/src/elab/program.ts +56 -0
- package/src/ir.ts +223 -0
- package/src/layout.ts +77 -0
- package/src/parse/ast.ts +89 -0
- package/src/parse/lexer.ts +146 -0
- package/src/parse/parser.ts +746 -0
- package/src/resolve.ts +35 -0
- package/src/standalone.ts +47 -0
- package/src/types.ts +109 -0
- package/README.md +0 -3
package/dist/builder.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { IR_VERSION } from "./ir.js";
|
|
2
|
+
import { T } from "./types.js";
|
|
3
|
+
/** Nesting depth of lambdas under construction: parameter names depend only on it, so the IR is reproducible. */
|
|
4
|
+
let depth = 0;
|
|
5
|
+
export function ir(x) {
|
|
6
|
+
if (typeof x === 'boolean')
|
|
7
|
+
return { e: 'lit', type: T.bool, value: x };
|
|
8
|
+
if (typeof x === 'number') {
|
|
9
|
+
if (!Number.isInteger(x))
|
|
10
|
+
throw new Error(`not an integer: ${x}`);
|
|
11
|
+
return { e: 'lit', type: x < 0 ? T.int : T.nat, value: x };
|
|
12
|
+
}
|
|
13
|
+
return x.ir;
|
|
14
|
+
}
|
|
15
|
+
const call = (fn, ...args) => wrap({ e: 'call', fn, args: args.map(ir) });
|
|
16
|
+
/** A lambda; parameter types are inferred by the checker. */
|
|
17
|
+
function lambda(arity, f) {
|
|
18
|
+
const params = Array.from({ length: arity }, (_, i) => ({ name: arity === 1 ? `x${depth}` : `x${depth}_${i}` }));
|
|
19
|
+
depth++;
|
|
20
|
+
try {
|
|
21
|
+
return { e: 'fn', params, body: ir(f(...params.map(p => v(p.name)))) };
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
depth--;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const methods = {
|
|
28
|
+
not: self => ({ e: 'call', fn: 'not', args: [self] }),
|
|
29
|
+
and: (self, ...xs) => ({ e: 'call', fn: 'and', args: [self, ...xs.map(ir)] }),
|
|
30
|
+
or: (self, ...xs) => ({ e: 'call', fn: 'or', args: [self, ...xs.map(ir)] }),
|
|
31
|
+
length: self => ({ e: 'call', fn: 'length', args: [self] }),
|
|
32
|
+
isEmpty: self => ({ e: 'call', fn: 'isEmpty', args: [self] }),
|
|
33
|
+
isSome: self => ({ e: 'call', fn: 'isSome', args: [self] }),
|
|
34
|
+
isNone: self => ({ e: 'call', fn: 'isNone', args: [self] }),
|
|
35
|
+
sum: self => ({ e: 'call', fn: 'sum', args: [self] }),
|
|
36
|
+
toMultiset: self => ({ e: 'call', fn: 'toMultiset', args: [self] }),
|
|
37
|
+
toList: self => ({ e: 'call', fn: 'toList', args: [self] }),
|
|
38
|
+
dedup: self => ({ e: 'call', fn: 'dedup', args: [self] }),
|
|
39
|
+
has: (self, x) => ({ e: 'call', fn: 'mem', args: [ir(x), self] }),
|
|
40
|
+
with: (self, fields) => ({ e: 'with', on: self, fields: mapValues(fields, ir) }),
|
|
41
|
+
};
|
|
42
|
+
for (const fn of ['implies', 'eq', 'ne', 'lt', 'le', 'gt', 'ge', 'add', 'sub', 'mul', 'div', 'mod', 'getD', 'at', 'append', 'diff']) {
|
|
43
|
+
methods[fn] = (self, x) => ({ e: 'call', fn, args: [self, ir(x)] });
|
|
44
|
+
}
|
|
45
|
+
for (const fn of ['filter', 'map', 'any', 'all', 'count']) {
|
|
46
|
+
methods[fn] = (self, f) => ({ e: 'call', fn, args: [self, lambda(1, f)] });
|
|
47
|
+
}
|
|
48
|
+
export function wrap(expr) {
|
|
49
|
+
return new Proxy({ ir: expr }, {
|
|
50
|
+
get(target, prop) {
|
|
51
|
+
if (prop === 'ir')
|
|
52
|
+
return expr;
|
|
53
|
+
if (typeof prop !== 'string')
|
|
54
|
+
return target[prop];
|
|
55
|
+
const m = methods[prop];
|
|
56
|
+
if (m)
|
|
57
|
+
return (...args) => wrap(m(expr, ...args));
|
|
58
|
+
return wrap({ e: 'get', on: expr, field: prop });
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function mapValues(o, f) {
|
|
63
|
+
return Object.fromEntries(Object.entries(o).map(([k, a]) => [k, f(a)]));
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Expressions
|
|
67
|
+
export const v = (name) => wrap({ e: 'var', name });
|
|
68
|
+
export const lit = (type, value) => wrap({ e: 'lit', type, value });
|
|
69
|
+
export const nat = (n) => lit(T.nat, n);
|
|
70
|
+
export const int = (n) => lit(T.int, n);
|
|
71
|
+
export const str = (s) => lit(T.string, s);
|
|
72
|
+
export const move = (m) => lit(T.move, m);
|
|
73
|
+
export const verdict = (s) => lit(T.verdict, s);
|
|
74
|
+
export const ctor = (type, name) => lit(type, name);
|
|
75
|
+
export const none = (of) => lit(T.option(of), null);
|
|
76
|
+
export const unit = () => lit(T.unit, null);
|
|
77
|
+
export const emptyList = (of) => lit(T.list(of), []);
|
|
78
|
+
export const some = (x) => call('some', x);
|
|
79
|
+
export const param = (name) => wrap({ e: 'param', name });
|
|
80
|
+
export const not = (x) => call('not', x);
|
|
81
|
+
export const and = (...xs) => call('and', ...xs);
|
|
82
|
+
export const or = (...xs) => call('or', ...xs);
|
|
83
|
+
export const min = (a, b) => call('min', a, b);
|
|
84
|
+
export const max = (a, b) => call('max', a, b);
|
|
85
|
+
export function ite(cond, then, otherwise) {
|
|
86
|
+
return wrap({ e: 'if', cond: ir(cond), then: ir(then), else: ir(otherwise) });
|
|
87
|
+
}
|
|
88
|
+
export function match(on, cases, otherwise) {
|
|
89
|
+
return wrap({ e: 'match', on: ir(on), cases: mapValues(cases, ir), ...(otherwise === undefined ? {} : { else: ir(otherwise) }) });
|
|
90
|
+
}
|
|
91
|
+
/** `let name := value; body`. */
|
|
92
|
+
export function let_(name, value, body) {
|
|
93
|
+
return wrap({ e: 'let', name, value: ir(value), body: ir(body(v(name))) });
|
|
94
|
+
}
|
|
95
|
+
export function rec(fields) {
|
|
96
|
+
return wrap({ e: 'record', fields: mapValues(fields, ir) });
|
|
97
|
+
}
|
|
98
|
+
/** An adversary choice; `site` names it in witnesses. */
|
|
99
|
+
export function choose(site, from, opts = {}) {
|
|
100
|
+
return wrap({
|
|
101
|
+
e: 'choose', site, from: ir(from),
|
|
102
|
+
...(opts.such ? { such: lambda(1, opts.such) } : {}),
|
|
103
|
+
...(opts.label ? { label: lambda(1, opts.label) } : {}),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** A hook's result: raise a flag on a collection of agents, or on those satisfying a predicate. */
|
|
107
|
+
export function effect(e = {}) {
|
|
108
|
+
return wrap({
|
|
109
|
+
e: 'effect',
|
|
110
|
+
...(e.world ? { world: mapValues(e.world, ir) } : {}),
|
|
111
|
+
...(e.raise ? { raise: mapValues(e.raise, r => typeof r === 'function' ? lambda(1, r) : ir(r)) } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
export const nothing = () => effect();
|
|
115
|
+
/** A call to a definition of the model or of the piece. */
|
|
116
|
+
export const callDef = (name, ...args) => call(name, ...args);
|
|
117
|
+
// Builtins that are not methods
|
|
118
|
+
export const agents = (C) => call('agents', C);
|
|
119
|
+
export const nodes = (C) => call('nodes', C);
|
|
120
|
+
export const size = (C) => call('size', C);
|
|
121
|
+
export const correct = (a) => call('correct', a);
|
|
122
|
+
export const present = (a) => call('present', a);
|
|
123
|
+
export const decisionOf = (a) => call('decisionOf', a);
|
|
124
|
+
export const target = (n, m) => call('target', n, m);
|
|
125
|
+
export const edgePresent = (n, m) => call('edgePresent', n, m);
|
|
126
|
+
export const offset = (n, frame, i) => call('offset', n, frame, i);
|
|
127
|
+
export const toGlobal = (frame, m) => call('toGlobal', frame, m);
|
|
128
|
+
export const opposite = (m) => call('opposite', m);
|
|
129
|
+
export const frames = () => call('frames');
|
|
130
|
+
export const node = (n) => call('node', n);
|
|
131
|
+
export const range = (n) => call('range', n);
|
|
132
|
+
export const subsets = (l) => call('subsets', l);
|
|
133
|
+
export const subsetsUpTo = (l, k) => call('subsetsUpTo', l, k);
|
|
134
|
+
export const ofFn = (n, f) => wrap({ e: 'call', fn: 'ofFn', args: [ir(n), lambda(1, f)] });
|
|
135
|
+
export const show = (x) => call('show', x);
|
|
136
|
+
export const concat = (...xs) => call('concat', ...xs.map(x => typeof x === 'string' ? str(x) : x));
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Pieces
|
|
139
|
+
const fields = (o) => o && Object.entries(o).map(([name, type]) => ({ name, type }));
|
|
140
|
+
export function flag(name, visibility, acts) {
|
|
141
|
+
return { name, visibility, acts };
|
|
142
|
+
}
|
|
143
|
+
/** A sensor: `C`, `me` and `frame` are in scope. */
|
|
144
|
+
export function sensor(name, type, body, equivariance) {
|
|
145
|
+
return { name, type, body: ir(body({ C: v('C'), me: v('me'), frame: v('frame') })), ...(equivariance ? { equivariance } : {}) };
|
|
146
|
+
}
|
|
147
|
+
export function def(name, params, ret, body) {
|
|
148
|
+
const ps = fields(params);
|
|
149
|
+
return { name, params: ps, ret, body: ir(body(Object.fromEntries(ps.map(p => [p.name, v(p.name)])))) };
|
|
150
|
+
}
|
|
151
|
+
export function component(spec) {
|
|
152
|
+
const p = Object.fromEntries(Object.keys(spec.params ?? {}).map(name => [name, v(name)]));
|
|
153
|
+
const hook = (h) => h && ir(h(v('C'), p));
|
|
154
|
+
const c = {
|
|
155
|
+
name: spec.name,
|
|
156
|
+
params: fields(spec.params),
|
|
157
|
+
args: spec.args,
|
|
158
|
+
world: fields(spec.world),
|
|
159
|
+
agent: fields(spec.agent),
|
|
160
|
+
flags: spec.flags,
|
|
161
|
+
sensors: spec.sensors,
|
|
162
|
+
actions: spec.actions,
|
|
163
|
+
init: spec.init && dropUndefined({
|
|
164
|
+
world: spec.init.world && mapValues(spec.init.world, x => ir(typeof x === 'function' ? x(p) : x)),
|
|
165
|
+
agent: spec.init.agent && mapValues(spec.init.agent, f => ir(f(v('i'), p))),
|
|
166
|
+
}),
|
|
167
|
+
beforeLook: hook(spec.beforeLook),
|
|
168
|
+
beforeMove: hook(spec.beforeMove),
|
|
169
|
+
afterMove: hook(spec.afterMove),
|
|
170
|
+
defs: spec.defs,
|
|
171
|
+
equivariance: spec.equivariance,
|
|
172
|
+
};
|
|
173
|
+
return dropUndefined(c);
|
|
174
|
+
}
|
|
175
|
+
export function model(spec) {
|
|
176
|
+
return dropUndefined({
|
|
177
|
+
name: spec.name,
|
|
178
|
+
topology: spec.topology,
|
|
179
|
+
sensors: spec.sensors,
|
|
180
|
+
memory: spec.memory ?? 'persistent',
|
|
181
|
+
frame: spec.frame ?? { kind: 'common' },
|
|
182
|
+
round: spec.round ?? 'fsync',
|
|
183
|
+
movement: 'rigid',
|
|
184
|
+
components: spec.components ?? [],
|
|
185
|
+
symmetries: spec.symmetries ?? 'derived',
|
|
186
|
+
defs: spec.defs,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
export const ring = (size) => ({ kind: 'ring', size: ir(size) });
|
|
190
|
+
export const dynamicRing = (size, k = 1) => ({ kind: 'dynamicRing', size: ir(size), k });
|
|
191
|
+
export function algorithm(spec) {
|
|
192
|
+
const mem = spec.mem ?? T.unit;
|
|
193
|
+
const s = { mem: v('mem'), pub: v('pub'), view: v('view') };
|
|
194
|
+
const step = spec.step ? spec.step(s) : { move: move('stay') };
|
|
195
|
+
return dropUndefined({
|
|
196
|
+
name: spec.name,
|
|
197
|
+
uses: spec.uses,
|
|
198
|
+
pub: spec.pub,
|
|
199
|
+
mem,
|
|
200
|
+
init: { pub: ir(spec.init.pub(v('i'))), mem: ir(spec.init.mem ? spec.init.mem(v('i')) : unit()) },
|
|
201
|
+
step: rec({ pub: s.pub, mem: s.mem, ...step }).ir,
|
|
202
|
+
line: spec.line && ir(spec.line(v('mem'))),
|
|
203
|
+
native: spec.native,
|
|
204
|
+
defs: spec.defs,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
export function task(spec) {
|
|
208
|
+
return dropUndefined({ name: spec.name, goal: spec.goal, verdict: ir(spec.verdict(v('C'))), equivariance: spec.equivariance, defs: spec.defs });
|
|
209
|
+
}
|
|
210
|
+
export function experiment(spec) {
|
|
211
|
+
const params = (spec.params ?? []).map(p => dropUndefined({ name: p.name, type: p.type, domain: p.domain === undefined ? undefined : ir(p.domain) }));
|
|
212
|
+
return dropUndefined({
|
|
213
|
+
irVersion: IR_VERSION, name: spec.name, model: spec.model, algorithm: spec.algorithm, task: spec.task, params,
|
|
214
|
+
agents: { count: ir(spec.agents.count), pos: ir(spec.agents.pos(v('i'))) },
|
|
215
|
+
limits: spec.limits,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/** Keeps the IR free of `undefined`, so that it round-trips through JSON unchanged. */
|
|
219
|
+
function dropUndefined(o) {
|
|
220
|
+
return Object.fromEntries(Object.entries(o).filter(([, x]) => x !== undefined));
|
|
221
|
+
}
|
package/dist/builtins.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { isCollection, isData, showType, T, typeEq } from "./types.js";
|
|
2
|
+
const err = (msg) => msg;
|
|
3
|
+
function mentionsAgent(t) {
|
|
4
|
+
switch (t.k) {
|
|
5
|
+
case 'agent': return true;
|
|
6
|
+
case 'option':
|
|
7
|
+
case 'list':
|
|
8
|
+
case 'multiset': return mentionsAgent(t.of);
|
|
9
|
+
case 'record': return t.fields.some(f => mentionsAgent(f.type));
|
|
10
|
+
default: return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function same(name) {
|
|
14
|
+
return ([a, b]) => typeEq(a, b) ? (isData(a) ? T.bool : err(`${name}: cannot compare ${showType(a)}`))
|
|
15
|
+
: err(`${name}: ${showType(a)} and ${showType(b)} differ`);
|
|
16
|
+
}
|
|
17
|
+
function numeric(name, ret) {
|
|
18
|
+
return (args) => {
|
|
19
|
+
const t = args[0];
|
|
20
|
+
if (t.k !== 'nat' && t.k !== 'int')
|
|
21
|
+
return err(`${name}: expected Nat or Int, got ${showType(t)}`);
|
|
22
|
+
if (!args.every(a => typeEq(a, t)))
|
|
23
|
+
return err(`${name}: arguments must have the same type`);
|
|
24
|
+
return ret ?? t;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Numbers, or lists of Bool or Nat in lexicographic order (`false < true`, a prefix first), as in Lean. */
|
|
28
|
+
function ordered(name) {
|
|
29
|
+
return (args) => {
|
|
30
|
+
const [a, b] = args;
|
|
31
|
+
if (a.k === 'list' && (a.of.k === 'bool' || a.of.k === 'nat')) {
|
|
32
|
+
return typeEq(a, b) ? T.bool : err(`${name}: ${showType(a)} and ${showType(b)} differ`);
|
|
33
|
+
}
|
|
34
|
+
return numeric(name, T.bool)(args);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function bools(name) {
|
|
38
|
+
return (args) => args.every(a => a.k === 'bool') ? T.bool : err(`${name}: expected Bool arguments`);
|
|
39
|
+
}
|
|
40
|
+
function collection(name, t) {
|
|
41
|
+
return isCollection(t) ? undefined : err(`${name}: expected a List or Multiset, got ${showType(t)}`);
|
|
42
|
+
}
|
|
43
|
+
const elem = (name) => ([l]) => collection(name, l) ?? [l.of];
|
|
44
|
+
function predicate(name, ret) {
|
|
45
|
+
return {
|
|
46
|
+
arity: 2,
|
|
47
|
+
lambdas: { 1: elem(name) },
|
|
48
|
+
ret: ([l, f]) => collection(name, l)
|
|
49
|
+
?? (f.k === 'fn' && f.ret.k === 'bool' ? ret(l) : err(`${name}: expected a predicate`)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const CONFIG_CONTEXTS = ['init', 'sensor', 'hook', 'beforeMove', 'task', 'def'];
|
|
53
|
+
export const BUILTINS = {
|
|
54
|
+
// Logic and comparison
|
|
55
|
+
not: { arity: 1, ret: bools('not') },
|
|
56
|
+
and: { ret: bools('and') },
|
|
57
|
+
or: { ret: bools('or') },
|
|
58
|
+
implies: { arity: 2, ret: bools('implies') },
|
|
59
|
+
eq: { arity: 2, ret: same('eq') },
|
|
60
|
+
ne: { arity: 2, ret: same('ne') },
|
|
61
|
+
lt: { arity: 2, ret: ordered('lt') },
|
|
62
|
+
le: { arity: 2, ret: ordered('le') },
|
|
63
|
+
gt: { arity: 2, ret: ordered('gt') },
|
|
64
|
+
ge: { arity: 2, ret: ordered('ge') },
|
|
65
|
+
// Arithmetic, following Lean: subtraction and division on Nat are truncated.
|
|
66
|
+
add: { arity: 2, ret: numeric('add') },
|
|
67
|
+
sub: { arity: 2, ret: numeric('sub') },
|
|
68
|
+
mul: { arity: 2, ret: numeric('mul') },
|
|
69
|
+
div: { arity: 2, ret: numeric('div') },
|
|
70
|
+
mod: { arity: 2, ret: numeric('mod') },
|
|
71
|
+
min: { arity: 2, ret: numeric('min') },
|
|
72
|
+
max: { arity: 2, ret: numeric('max') },
|
|
73
|
+
toInt: { arity: 1, ret: ([a]) => a.k === 'nat' ? T.int : err('toInt: expected Nat') },
|
|
74
|
+
// Options
|
|
75
|
+
some: { arity: 1, ret: ([a]) => T.option(a) },
|
|
76
|
+
isSome: { arity: 1, ret: ([o]) => o.k === 'option' ? T.bool : err('isSome: expected an Option') },
|
|
77
|
+
isNone: { arity: 1, ret: ([o]) => o.k === 'option' ? T.bool : err('isNone: expected an Option') },
|
|
78
|
+
getD: {
|
|
79
|
+
arity: 2,
|
|
80
|
+
ret: ([o, d]) => o.k === 'option' && typeEq(o.of, d) ? d : err('getD: expected an Option and a default of its type'),
|
|
81
|
+
},
|
|
82
|
+
// Collections (List and Multiset)
|
|
83
|
+
length: { arity: 1, ret: ([l]) => collection('length', l) ?? T.nat },
|
|
84
|
+
isEmpty: { arity: 1, ret: ([l]) => collection('isEmpty', l) ?? T.bool },
|
|
85
|
+
filter: predicate('filter', l => l),
|
|
86
|
+
any: predicate('any', () => T.bool),
|
|
87
|
+
all: predicate('all', () => T.bool),
|
|
88
|
+
count: predicate('count', () => T.nat),
|
|
89
|
+
map: {
|
|
90
|
+
arity: 2,
|
|
91
|
+
lambdas: { 1: elem('map') },
|
|
92
|
+
ret: ([l, f]) => collection('map', l) ?? (f.k === 'fn' ? { k: l.k, of: f.ret } : err('map: expected a function')),
|
|
93
|
+
},
|
|
94
|
+
sum: { arity: 1, ret: ([l]) => isCollection(l) && l.of.k === 'nat' ? T.nat : err('sum: expected a collection of Nat') },
|
|
95
|
+
mem: { arity: 2, ret: ([x, l]) => collection('mem', l) ?? (typeEq(x, l.of) ? T.bool : err('mem: element type differs')) },
|
|
96
|
+
range: { arity: 1, ret: ([n]) => n.k === 'nat' ? T.list(T.nat) : err('range: expected Nat') },
|
|
97
|
+
toMultiset: { arity: 1, ret: ([l]) => l.k === 'list' ? T.multiset(l.of) : err('toMultiset: expected a List') },
|
|
98
|
+
/** The elements in canonical order; not for agents, which have no order. */
|
|
99
|
+
toList: { arity: 1, ret: ([l]) => l.k !== 'multiset' ? err('toList: expected a Multiset') : mentionsAgent(l.of) ? err('toList: anonymous agents have no order') : T.list(l.of) },
|
|
100
|
+
/** All sub-collections, smallest first. */
|
|
101
|
+
subsets: { arity: 1, ret: ([l]) => collection('subsets', l) ?? T.list(l) },
|
|
102
|
+
/** Sub-collections with at most `k` elements. */
|
|
103
|
+
subsetsUpTo: {
|
|
104
|
+
arity: 2,
|
|
105
|
+
ret: ([l, k]) => collection('subsetsUpTo', l) ?? (k.k === 'nat' ? T.list(l) : err('subsetsUpTo: expected a Nat bound')),
|
|
106
|
+
},
|
|
107
|
+
/** Removes repeated elements. */
|
|
108
|
+
dedup: { arity: 1, ret: ([l]) => collection('dedup', l) ?? l },
|
|
109
|
+
/** Multiset difference. */
|
|
110
|
+
diff: { arity: 2, ret: ([a, b]) => a.k === 'multiset' && typeEq(a, b) ? a : err('diff: expected two Multisets of the same type') },
|
|
111
|
+
/** `[a, b, c]` (an empty list is a literal). */
|
|
112
|
+
list: { ret: args => args.every(a => typeEq(a, args[0])) ? T.list(args[0]) : err('list: elements of different types') },
|
|
113
|
+
at: { arity: 2, ret: ([l, i]) => l.k === 'list' && i.k === 'nat' ? l.of : err('at: expected a List and a Nat') },
|
|
114
|
+
append: { arity: 2, ret: ([a, b]) => isCollection(a) && typeEq(a, b) ? a : err('append: expected two collections of the same type') },
|
|
115
|
+
ofFn: {
|
|
116
|
+
arity: 2,
|
|
117
|
+
lambdas: { 1: () => [T.nat] },
|
|
118
|
+
ret: ([n, f]) => n.k === 'nat' && f.k === 'fn' ? T.list(f.ret) : err('ofFn: expected a Nat and a function'),
|
|
119
|
+
},
|
|
120
|
+
// Labels
|
|
121
|
+
show: { arity: 1, ret: () => T.string },
|
|
122
|
+
concat: { ret: args => args.every(a => a.k === 'string') ? T.string : err('concat: expected strings') },
|
|
123
|
+
// Configurations and agents
|
|
124
|
+
/** A multiset: anonymous agents have no order. */
|
|
125
|
+
agents: { arity: 1, config: true, only: CONFIG_CONTEXTS, ret: ([c]) => c.k === 'config' ? T.multiset(T.agent) : err('agents: expected a Config') },
|
|
126
|
+
nodes: { arity: 1, config: true, only: CONFIG_CONTEXTS, ret: ([c]) => c.k === 'config' ? T.list(T.node) : err('nodes: expected a Config') },
|
|
127
|
+
size: { arity: 1, config: true, only: CONFIG_CONTEXTS, ret: ([c]) => c.k === 'config' ? T.nat : err('size: expected a Config') },
|
|
128
|
+
/** No flag is raised. */
|
|
129
|
+
correct: { arity: 1, config: true, ret: ([a]) => a.k === 'agent' ? T.bool : err('correct: expected an Agent') },
|
|
130
|
+
/** No hidden flag is raised. */
|
|
131
|
+
present: { arity: 1, config: true, ret: ([a]) => a.k === 'agent' ? T.bool : err('present: expected an Agent') },
|
|
132
|
+
/** The decision of an agent this round; `decided` is false for agents that did not decide (then `move` is `stay`). */
|
|
133
|
+
decisionOf: { arity: 1, config: true, only: ['beforeMove'], ret: ([a]) => a.k === 'agent' ? T.decision : err('decisionOf: expected an Agent') },
|
|
134
|
+
// Topology
|
|
135
|
+
/** The neighbour in a global direction (whether or not the edge is present). */
|
|
136
|
+
target: { arity: 2, config: true, ret: ([n, m]) => n.k === 'node' && m.k === 'move' ? T.node : err('target: expected a Node and a Move') },
|
|
137
|
+
/** The node reached from a node after `i` steps in a global direction (edges present or not). */
|
|
138
|
+
advance: {
|
|
139
|
+
arity: 3, config: true,
|
|
140
|
+
ret: ([n, m, i]) => n.k === 'node' && m.k === 'move' && i.k === 'nat' ? T.node : err('advance: expected a Node, a Move and a Nat'),
|
|
141
|
+
},
|
|
142
|
+
/** Whether the edge from a node in a global direction is present in this round. */
|
|
143
|
+
edgePresent: { arity: 2, config: true, ret: ([n, m]) => n.k === 'node' && m.k === 'move' ? T.bool : err('edgePresent: expected a Node and a Move') },
|
|
144
|
+
/** The node seen at local offset `i` from a node, in a frame. */
|
|
145
|
+
offset: {
|
|
146
|
+
arity: 3, config: true,
|
|
147
|
+
ret: ([n, f, i]) => n.k === 'node' && f.k === 'frame' && i.k === 'nat' ? T.node : err('offset: expected a Node, a Frame and a Nat'),
|
|
148
|
+
},
|
|
149
|
+
/** A local move, mapped to a global one through a frame. */
|
|
150
|
+
toGlobal: { arity: 2, ret: ([f, m]) => f.k === 'frame' && m.k === 'move' ? T.move : err('toGlobal: expected a Frame and a Move') },
|
|
151
|
+
opposite: { arity: 1, ret: ([m]) => m.k === 'move' ? T.move : err('opposite: expected a Move') },
|
|
152
|
+
/** Every frame of the topology. */
|
|
153
|
+
frames: { arity: 0, ret: () => T.list(T.frame) },
|
|
154
|
+
/** Node number `n` (modulo the size). Only in initialisation. */
|
|
155
|
+
node: { arity: 1, config: true, absolute: true, only: ['init', 'domain'], ret: ([n]) => n.k === 'nat' ? T.node : err('node: expected a Nat') },
|
|
156
|
+
};
|