@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/src/resolve.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces the literals of type `infer` (`.ctor`, `none`, `[]` from the front
|
|
3
|
+
* end) by literals of the types the checker found, and folds lists of
|
|
4
|
+
* literals into a single literal. The result is plain IR: the interpreter
|
|
5
|
+
* and the Lean generator never see `infer`.
|
|
6
|
+
*/
|
|
7
|
+
import type { Experiment, Expr, Json, Type } from './ir';
|
|
8
|
+
import { T } from './types';
|
|
9
|
+
|
|
10
|
+
export function resolveTypes(exp: Experiment, resolutions: Map<Expr, Type>, onCopy?: (from: Expr, to: Expr) => void): Experiment {
|
|
11
|
+
const copy = (x: unknown): unknown => {
|
|
12
|
+
if (!x || typeof x !== 'object') return x;
|
|
13
|
+
if (Array.isArray(x)) return x.map(copy);
|
|
14
|
+
const o = x as Record<string, unknown>;
|
|
15
|
+
if (typeof o.e === 'string') {
|
|
16
|
+
const e = o as Expr;
|
|
17
|
+
let out: Expr;
|
|
18
|
+
if (e.e === 'lit' && e.type.k === 'infer') {
|
|
19
|
+
const t = resolutions.get(e);
|
|
20
|
+
if (!t) throw new Error(`a literal was not resolved: ${JSON.stringify(e.value)}`);
|
|
21
|
+
out = { e: 'lit', type: t, value: e.value };
|
|
22
|
+
} else {
|
|
23
|
+
out = Object.fromEntries(Object.entries(o).map(([k, v]) => [k, copy(v)])) as Expr;
|
|
24
|
+
if (out.e === 'call' && out.fn === 'list' && out.args.every(a => a.e === 'lit')) {
|
|
25
|
+
const items = out.args as Extract<Expr, { e: 'lit' }>[];
|
|
26
|
+
out = { e: 'lit', type: T.list(items[0].type), value: items.map(a => a.value) as Json[] };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
onCopy?.(e, out);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
return Object.fromEntries(Object.entries(o).map(([k, v]) => [k, copy(v)]));
|
|
33
|
+
};
|
|
34
|
+
return copy(exp) as Experiment;
|
|
35
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks and resolves a component on its own, in the smallest experiment
|
|
3
|
+
* that contains it. The Lean library is generated from components resolved
|
|
4
|
+
* this way, independently of any model.
|
|
5
|
+
*/
|
|
6
|
+
import { check } from './check';
|
|
7
|
+
import type { Component, Experiment, Expr, Param, Type } from './ir';
|
|
8
|
+
import { IR_VERSION } from './ir';
|
|
9
|
+
import { resolveTypes } from './resolve';
|
|
10
|
+
import { T } from './types';
|
|
11
|
+
|
|
12
|
+
/** Experiment parameters read by the component's initial values (`site := param site`), with their types. */
|
|
13
|
+
function inputs(c: Component): Param[] {
|
|
14
|
+
const found = new Map<string, Type>();
|
|
15
|
+
const fields = [...(c.world ?? []), ...(c.agent ?? [])];
|
|
16
|
+
for (const f of fields) {
|
|
17
|
+
const init = c.init?.world?.[f.name] ?? c.init?.agent?.[f.name];
|
|
18
|
+
if (init?.e === 'param') found.set(init.name, f.type);
|
|
19
|
+
}
|
|
20
|
+
return [...found].map(([name, type]) => ({ name, type }));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function standaloneExperiment(c: Component): Experiment {
|
|
24
|
+
const lit = (type: Type, value: unknown): Expr => ({ e: 'lit', type, value } as Expr);
|
|
25
|
+
return {
|
|
26
|
+
irVersion: IR_VERSION,
|
|
27
|
+
name: `${c.name} alone`,
|
|
28
|
+
model: {
|
|
29
|
+
name: c.name, topology: { kind: 'ring', size: { e: 'param', name: 'N' } }, sensors: [], memory: 'persistent',
|
|
30
|
+
frame: { kind: 'common' }, round: 'fsync', movement: 'rigid', components: [c], symmetries: 'derived',
|
|
31
|
+
},
|
|
32
|
+
algorithm: {
|
|
33
|
+
name: 'idle', uses: [], pub: T.unit, mem: T.unit, init: { pub: lit(T.unit, null), mem: lit(T.unit, null) },
|
|
34
|
+
step: { e: 'record', fields: { move: lit(T.move, 'stay'), pub: { e: 'var', name: 'pub' }, mem: { e: 'var', name: 'mem' } } },
|
|
35
|
+
},
|
|
36
|
+
task: { name: 'none', goal: 'safety', verdict: lit(T.verdict, 'inProgress') },
|
|
37
|
+
params: [{ name: 'N', type: T.nat }, ...inputs(c)],
|
|
38
|
+
agents: { count: lit(T.nat, 1), pos: lit(T.node, 0) },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveComponent(c: Component): Component {
|
|
43
|
+
const exp = standaloneExperiment(c);
|
|
44
|
+
const checked = check(exp);
|
|
45
|
+
if (checked.errors.length) throw new Error(`${c.name}:\n${checked.errors.join('\n')}`);
|
|
46
|
+
return resolveTypes(exp, checked.resolutions).model.components[0];
|
|
47
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Field, Type } from './ir';
|
|
2
|
+
|
|
3
|
+
export const T = {
|
|
4
|
+
unit: { k: 'unit' } as Type,
|
|
5
|
+
bool: { k: 'bool' } as Type,
|
|
6
|
+
nat: { k: 'nat' } as Type,
|
|
7
|
+
int: { k: 'int' } as Type,
|
|
8
|
+
string: { k: 'string' } as Type,
|
|
9
|
+
node: { k: 'node' } as Type,
|
|
10
|
+
move: { k: 'move' } as Type,
|
|
11
|
+
frame: { k: 'frame' } as Type,
|
|
12
|
+
agent: { k: 'agent' } as Type,
|
|
13
|
+
config: { k: 'config' } as Type,
|
|
14
|
+
effect: { k: 'effect' } as Type,
|
|
15
|
+
decision: { k: 'decision' } as Type,
|
|
16
|
+
verdict: { k: 'verdict' } as Type,
|
|
17
|
+
pub: { k: 'pub' } as Type,
|
|
18
|
+
infer: { k: 'infer' } as Type,
|
|
19
|
+
enum: (name: string, ctors: string[]): Type => ({ k: 'enum', name, ctors }),
|
|
20
|
+
option: (of: Type): Type => ({ k: 'option', of }),
|
|
21
|
+
record: (fields: Record<string, Type> | Field[]): Type =>
|
|
22
|
+
({ k: 'record', fields: Array.isArray(fields) ? fields : Object.entries(fields).map(([name, type]) => ({ name, type })) }),
|
|
23
|
+
list: (of: Type): Type => ({ k: 'list', of }),
|
|
24
|
+
multiset: (of: Type): Type => ({ k: 'multiset', of }),
|
|
25
|
+
fn: (params: Type[], ret: Type): Type => ({ k: 'fn', params, ret }),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const MOVES = ['stay', 'cw', 'ccw'] as const;
|
|
29
|
+
export const VERDICTS = ['success', 'failure', 'inProgress'] as const;
|
|
30
|
+
|
|
31
|
+
export function typeEq(a: Type, b: Type): boolean {
|
|
32
|
+
if (a.k !== b.k) return false;
|
|
33
|
+
switch (a.k) {
|
|
34
|
+
case 'enum': {
|
|
35
|
+
const o = b as typeof a;
|
|
36
|
+
return a.name === o.name && a.ctors.length === o.ctors.length && a.ctors.every((c, i) => c === o.ctors[i]);
|
|
37
|
+
}
|
|
38
|
+
case 'option': case 'list': case 'multiset':
|
|
39
|
+
return typeEq(a.of, (b as typeof a).of);
|
|
40
|
+
case 'record': {
|
|
41
|
+
const o = b as typeof a;
|
|
42
|
+
// Fields are matched by name, as in Lean structure instances.
|
|
43
|
+
return a.fields.length === o.fields.length
|
|
44
|
+
&& a.fields.every(f => o.fields.some(g => g.name === f.name && typeEq(f.type, g.type)));
|
|
45
|
+
}
|
|
46
|
+
case 'fn': {
|
|
47
|
+
const o = b as typeof a;
|
|
48
|
+
return a.params.length === o.params.length && a.params.every((p, i) => typeEq(p, o.params[i])) && typeEq(a.ret, o.ret);
|
|
49
|
+
}
|
|
50
|
+
default:
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function showType(t: Type): string {
|
|
56
|
+
switch (t.k) {
|
|
57
|
+
case 'enum': return t.name;
|
|
58
|
+
case 'option': return `Option ${wrap(t.of)}`;
|
|
59
|
+
case 'list': return `List ${wrap(t.of)}`;
|
|
60
|
+
case 'multiset': return `Multiset ${wrap(t.of)}`;
|
|
61
|
+
case 'record': return `{ ${t.fields.map(f => `${f.name} : ${showType(f.type)}`).join(', ')} }`;
|
|
62
|
+
case 'fn': return `(${t.params.map(showType).join(' → ')} → ${showType(t.ret)})`;
|
|
63
|
+
default: return t.k[0].toUpperCase() + t.k.slice(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function wrap(t: Type) {
|
|
68
|
+
const s = showType(t);
|
|
69
|
+
return /\s/.test(s) && !s.startsWith('{') ? `(${s})` : s;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Types whose values can be compared, stored in configurations and encoded. */
|
|
73
|
+
export function isData(t: Type): boolean {
|
|
74
|
+
switch (t.k) {
|
|
75
|
+
case 'fn': case 'config': case 'effect': case 'decision': return false;
|
|
76
|
+
case 'option': case 'list': case 'multiset': return isData(t.of);
|
|
77
|
+
case 'record': return t.fields.every(f => isData(f.type));
|
|
78
|
+
default: return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Data that can be kept in a configuration: agents are only references within a round. */
|
|
83
|
+
export function isStorable(t: Type): boolean {
|
|
84
|
+
switch (t.k) {
|
|
85
|
+
case 'agent': return false;
|
|
86
|
+
case 'option': case 'list': case 'multiset': return isStorable(t.of);
|
|
87
|
+
case 'record': return t.fields.every(f => isStorable(f.type));
|
|
88
|
+
default: return isData(t);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isCollection(t: Type): t is { k: 'list' | 'multiset'; of: Type } {
|
|
93
|
+
return t.k === 'list' || t.k === 'multiset';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function fieldType(t: Type, name: string): Type | undefined {
|
|
97
|
+
return t.k === 'record' ? t.fields.find(f => f.name === name)?.type : undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Replaces the `pub` placeholder by the public state of the composition. */
|
|
101
|
+
export function resolvePub(t: Type, pub: Type): Type {
|
|
102
|
+
switch (t.k) {
|
|
103
|
+
case 'pub': return pub;
|
|
104
|
+
case 'option': case 'list': case 'multiset': return { k: t.k, of: resolvePub(t.of, pub) } as Type;
|
|
105
|
+
case 'record': return { k: 'record', fields: t.fields.map(f => ({ name: f.name, type: resolvePub(f.type, pub) })) };
|
|
106
|
+
case 'fn': return { k: 'fn', params: t.params.map(p => resolvePub(p, pub)), ret: resolvePub(t.ret, pub) };
|
|
107
|
+
default: return t;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/README.md
DELETED