@fraczak/k 3.0.1 → 3.1.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/BIG_PICTURE.md +220 -0
- package/Code-derivation-tests/blowup.mjs +27 -0
- package/Code-derivation-tests/bnat.mjs +4 -2
- package/Code-derivation-tests/built-in.mjs +1 -1
- package/Code-derivation-tests/dot.mjs +28 -3
- package/Code-derivation-tests/index.mjs +2 -2
- package/Code-derivation-tests/loop.mjs +1 -1
- package/Code-derivation-tests/nat.mjs +2 -2
- package/Code-derivation-tests/not-not.mjs +2 -2
- package/Code-derivation-tests/product.mjs +9 -5
- package/Code-derivation-tests/rlz.mjs +2 -2
- package/Code-derivation-tests/vector.mjs +8 -14
- package/Graph.mjs +95 -0
- package/TYPING.md +368 -0
- package/aaa.txt +203033 -0
- package/codes.mjs +60 -8
- package/hash.mjs +6 -5
- package/index.mjs +7 -38
- package/k.mjs +3 -3
- package/package.json +1 -1
- package/parser.jison +14 -6
- package/parser.mjs +68 -62
- package/patterns.mjs +386 -534
- package/repl.mjs +41 -9
- package/run.mjs +7 -5
- package/symbol-table.mjs +44 -41
- package/test-filters.mjs +3 -3
- package/typing-test.mjs +40 -0
- package/typing.mjs +528 -0
- package/TODO.md +0 -54
package/BIG_PICTURE.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
|
|
2
|
+
`Kernel-code` is an experiment aiming to define a "perfect programming language".
|
|
3
|
+
|
|
4
|
+
## Basics
|
|
5
|
+
|
|
6
|
+
The language describes "partial first-order functions" on "printable" values.
|
|
7
|
+
The printable values can be seen as rooted, edge-labeled trees, intuitively similar to an
|
|
8
|
+
XML or a JSON document. The values are always typed.
|
|
9
|
+
|
|
10
|
+
### Types (codes)
|
|
11
|
+
|
|
12
|
+
Formally, a _type_ is a (subclass of) finite tree automata. The type defines a set of its values,
|
|
13
|
+
which corresponds to the set of trees accepted by the automaton.
|
|
14
|
+
Therefore, every type admits a unique normal form in terms of its minimal deterministic tree automaton.
|
|
15
|
+
|
|
16
|
+
The set of all types can be seen as the universal deterministic (infinite) tree automaton.
|
|
17
|
+
Every node of that automaton defines a type; only a finite set of nodes are reachable from
|
|
18
|
+
any node.
|
|
19
|
+
|
|
20
|
+
Types are defined by two constructs:
|
|
21
|
+
|
|
22
|
+
1. Product
|
|
23
|
+
2. Union (also called tagged or disjoint union)
|
|
24
|
+
|
|
25
|
+
Given a set (possibly empty) of types `t1, t2, ..., tn` and a set of pairwise different tags (labels) `l1, l2, ..., ln`,
|
|
26
|
+
|
|
27
|
+
- by `{ t1 l1, t2 l2, ..., tn ln }`, we define a _product type_ with exactly `n` projection functions `.l1`, `.l2`, `...`, `.ln`,
|
|
28
|
+
mapping a value of type `{t1 l1, t2 l2, ..., tn ln}` into a value of type `t1`, `t2`, `...`, `tn`, respectively.
|
|
29
|
+
- by `< t1 l1, t2 l2, ..., tn ln >`, we define a _union type_ with exactly `n` projection functions `.l1`, `.l2`, `...`, `.ln`,
|
|
30
|
+
mapping a value of type `<t1 l1, t2 l2, ..., tn ln>` into a value of type `t1`, `t2`, `...`, `tn`, respectively.
|
|
31
|
+
|
|
32
|
+
Intuitively, the projection functions on a product type are all total (meaning, they are defined for every value of the
|
|
33
|
+
input product type), whereas in the case of a union type, exactly one projection function is defined
|
|
34
|
+
for a given value of the union type.
|
|
35
|
+
|
|
36
|
+
Some properties of these types:
|
|
37
|
+
|
|
38
|
+
1. Singleton product and singleton union types, `{ t label }` and `< t label >`, are equivalent as they have
|
|
39
|
+
the same set of values (and the same projection function `.label`). That's why, we not allow singleton product nor
|
|
40
|
+
singleton union types in the syntax of the language. The other benefit is that we will be able to use the same systax
|
|
41
|
+
for values of both product and union types, interpretting every singleton product-like value, e.g., `{ a_value label }`,
|
|
42
|
+
as a union type value (a variant).
|
|
43
|
+
2. The empty product type `{}` has a single value, called the _unit_ and also denoted by `{}`, which has no fields.
|
|
44
|
+
3. The empty union type `<>` has no values.
|
|
45
|
+
4. The product and union types form dual categories.
|
|
46
|
+
|
|
47
|
+
### Partial functions
|
|
48
|
+
|
|
49
|
+
A _partial function_ is a function that is not defined for every value of its domain.
|
|
50
|
+
|
|
51
|
+
There are three ways of combining partial functions:
|
|
52
|
+
|
|
53
|
+
1. __Composition__: we write a composition of `f` and `g` as `(f g)`, meaning that if `f` is defined on valye `x` producing `y`,
|
|
54
|
+
and `g` is defined on value `y` producing `z`, then `(f g)` is defined on value `x` producing `z`.
|
|
55
|
+
2. __Union__: we write `< f1, f2 >` for a union of two functions `f1` and `f2`, meaning that
|
|
56
|
+
if `f1` is defined on value `x` producing `y`, then `< f1, f2 >`
|
|
57
|
+
is defined on value `x` producing `y`. Otherwise, i.e., when `f1` is not defined for `x`, `< f1 f2 >` acts as `f2`.
|
|
58
|
+
3. __Product__: we write `{f1 lab1, f2 lab2 }` for a product of two functions `f1` and `f2`, meaning that if `f1` and `f2`
|
|
59
|
+
are both defined on value `x` producing `y1` and `y2`, respectively, then `{f1 lab1, f2 lab2 }`
|
|
60
|
+
is defined on value `x` producing `{y1 lab1, y2 lab2}`. Otherwise, `{f1 lab1, f2 lab2 }` is not defined for `x`.
|
|
61
|
+
|
|
62
|
+
Actually, all of the above operations take any number of arguments, not only two. For example,
|
|
63
|
+
empty composition, `()`, defines the identity function.
|
|
64
|
+
|
|
65
|
+
### Program
|
|
66
|
+
|
|
67
|
+
A `kernel-core` program is a set of definitions of types and partial functions followed by an _expression_,
|
|
68
|
+
which is the partial function the program defines.
|
|
69
|
+
|
|
70
|
+
The language syntax is defined by:
|
|
71
|
+
|
|
72
|
+
```bnf
|
|
73
|
+
program ::= (type_definition | function_definition) * expression
|
|
74
|
+
|
|
75
|
+
type_definition ::= '$' type_name '=' type ';'
|
|
76
|
+
type ::= '{' type_label_list '}' | '<' type_label_list '>'
|
|
77
|
+
type_label_list ::= /* empty */ | ( type_expression label_name ',' ) + type_expression label_name
|
|
78
|
+
type_expression ::= type_name | type
|
|
79
|
+
|
|
80
|
+
function_definition ::= function_name '=' expression ';'
|
|
81
|
+
expression ::= projection | typing | composition | union | product | function_name
|
|
82
|
+
projection ::= '.' label_name
|
|
83
|
+
typing ::= '$' tape_expression
|
|
84
|
+
composition ::= '(' expression * ')'
|
|
85
|
+
union ::= '<' (expression ',') + expression '>'
|
|
86
|
+
product ::= '{' expression_label_list '}'
|
|
87
|
+
expression_label_list ::= /* empty */ | ( expression label_name ',' ) * expression label
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
We assume that the `type_name`, `function_name`, and `label_name` are identifiers (strings) such that:
|
|
91
|
+
|
|
92
|
+
1. All defined type names are distinct.
|
|
93
|
+
2. All defined function names are distinct.
|
|
94
|
+
3. All label names within a type definition or a `product` expression are locally distinct.
|
|
95
|
+
|
|
96
|
+
The `typing` expression acts as a type annotation and can be seen as a 'filter', i.e., an identity
|
|
97
|
+
function defined only for the values of the corresponding type.
|
|
98
|
+
|
|
99
|
+
That's it.
|
|
100
|
+
No `int`, no `if` statement, no `loop`, no `throw`, no _closure_, no _annotations_, and no macros.
|
|
101
|
+
Just types and partial functions.
|
|
102
|
+
|
|
103
|
+
### Normalization
|
|
104
|
+
|
|
105
|
+
All type names are replaced by the hash of their canonical representation.
|
|
106
|
+
|
|
107
|
+
We will also replace the function name by the hash of the normalized unfolded definition of the function.
|
|
108
|
+
Obviously, two different definitions of the same function may have different hashes.
|
|
109
|
+
|
|
110
|
+
This approach solves the problem of modules and imports and opens the door to a universal
|
|
111
|
+
registry of types (schemas) and functions.
|
|
112
|
+
|
|
113
|
+
## Typing and Polymorphism (codes and filters/patterns)
|
|
114
|
+
|
|
115
|
+
Abstract Syntax Tree of a program after parsing consists of a dictionary of _type definitions_,
|
|
116
|
+
a dictionary of _function definitions_, and the final _main expression_.
|
|
117
|
+
|
|
118
|
+
In principle, the initial form of the Abstract Syntax Tree, _raw_ AST, is enough to evaluate
|
|
119
|
+
the program on an input.
|
|
120
|
+
|
|
121
|
+
Our objective however is to compute canonicals representations for types and normalized
|
|
122
|
+
representations for functions, so they could be effectively reused.
|
|
123
|
+
|
|
124
|
+
The normalization steps are:
|
|
125
|
+
|
|
126
|
+
1. Build the _type graph_ from all types used by the program, i.e., used in _type definitions_
|
|
127
|
+
as well as type expressions used in _function definitions_ and the _main expression_.
|
|
128
|
+
2. Annotate the expressions with types or type patterns; every node of the AST is annotated with a pair of type patterns.
|
|
129
|
+
3. Turn the _singleton_ type patterns into types and add them to the _type graph_. Go to (2) unless no change.
|
|
130
|
+
4. Normalize the function definitions by rewriting them as the fix-point of a single equation.
|
|
131
|
+
|
|
132
|
+
### Example
|
|
133
|
+
|
|
134
|
+
```k
|
|
135
|
+
$ bit = < {} o, {} i >;
|
|
136
|
+
bit0 = {{} o} $bit;
|
|
137
|
+
bit1 = {{} i} $bit;
|
|
138
|
+
|
|
139
|
+
$ byte = { bit b0, bit b1, bit b2, bit b3 };
|
|
140
|
+
zero = bit0 { () b0, () b1, () b2, () b3 } $byte;
|
|
141
|
+
|
|
142
|
+
inc = $byte
|
|
143
|
+
<
|
|
144
|
+
{ .b0.o bit0 overflown, {bit1 b0, .b1 b1, .b2 b2, .b3 b3 } byte },
|
|
145
|
+
{ .b1.o bit0 overflown, {bit0 b0, bit1 b1, .b2 b2, .b3 b3 } byte },
|
|
146
|
+
{ .b2.o bit0 overflown, {bit0 b0, bit0 b1, bit1 b2, .b3 b3 } byte },
|
|
147
|
+
{ .b3.o bit0 overflown, {bit0 b0, bit0 b1, bit0 b2, bit1 b3 } byte },
|
|
148
|
+
{ bit1 overflown, zero byte }
|
|
149
|
+
>
|
|
150
|
+
;
|
|
151
|
+
|
|
152
|
+
-- inc with overflown flag (polymorphism)
|
|
153
|
+
inc_o =
|
|
154
|
+
{ .byte inc inc, .overflown overflown }
|
|
155
|
+
{
|
|
156
|
+
.inc.byte byte,
|
|
157
|
+
<.overflown.i bit1, .inc.overflown > overflown
|
|
158
|
+
}
|
|
159
|
+
;
|
|
160
|
+
|
|
161
|
+
inc3 = inc inc_o inc_o;
|
|
162
|
+
|
|
163
|
+
inc3
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
In the above program, we define a type `bit` a union of two unit types,
|
|
167
|
+
and a type `byte` as a product, for simplicity, of four `bit`s.
|
|
168
|
+
|
|
169
|
+
Functions `bit0`, `bit1`, and `zero` are "constant polymorphic functions", meaning:
|
|
170
|
+
|
|
171
|
+
- constant: if defined, they always return exactly the same value;
|
|
172
|
+
- polymorphic: they are defined for more than one pair of input and output type: functions
|
|
173
|
+
`bit0` and `bit1` are of type `X -> bit`, and `zero` is of type `X -> byte`, where `X`
|
|
174
|
+
denotes an unconstrained type pattern.
|
|
175
|
+
|
|
176
|
+
Functions `inc` and `inc3` are (non-polymorphic) functions of types
|
|
177
|
+
`byte -> { bit overflown, byte byte }` and
|
|
178
|
+
`{ bit overflown, byte byte } -> { bit overflown, byte byte }`, respectively.
|
|
179
|
+
|
|
180
|
+
Function `inc_o` is a polymorphic function of type patterns: `X -> Y` with
|
|
181
|
+
following constraints:
|
|
182
|
+
|
|
183
|
+
- `X` is a product type with at least two fields: `byte` and `overflown`, denoted by:
|
|
184
|
+
> `?{ byte byte, Z overflown, ...}`;
|
|
185
|
+
- `Z` is a type (union or product) with field `i` denoted by:
|
|
186
|
+
> `?( V i, ...)`;
|
|
187
|
+
- `V` is unconstrained;
|
|
188
|
+
- `Y` is a product type with exactly two fields: `byte` and `overflown`, denoted by:
|
|
189
|
+
> `?{ byte byte, bit overflown }`;
|
|
190
|
+
|
|
191
|
+
The type pattern for `Y` is _singleton_, as only one type fits the pattern,
|
|
192
|
+
whereas the other type patterns are not.
|
|
193
|
+
|
|
194
|
+
In this example, we were able to find the most general type pattern for all sub-expressions of the program.
|
|
195
|
+
However, we still do not have a way to find the most general type patterns in the general case.
|
|
196
|
+
This is work in progress.
|
|
197
|
+
|
|
198
|
+
## Universal Schema Registry
|
|
199
|
+
|
|
200
|
+
Since the normalization process for types is fast and deterministic, we can build a universal schema registry that will store all invented types.
|
|
201
|
+
The registry will be a key-value store, where the key is the hash of the normalized type, and the value is the normalized type itself.
|
|
202
|
+
|
|
203
|
+
All functions (non-polymorphic and polymorphic) will be renamed by the hash of their normalized definition and stored in the similar key-value store.
|
|
204
|
+
|
|
205
|
+
Non-polymorphic functions can be easily indexed by the hashes of their input and output types so that we can quickly find the function we need, e.g., for autocomplition.
|
|
206
|
+
Searching for polymorphic functions is more complicated, as we need to find the function whose input and output type patterns fit given types.
|
|
207
|
+
|
|
208
|
+
## Serialization and Compilation
|
|
209
|
+
|
|
210
|
+
The language is designed to transform _codes_, i.e., serialized typed values, i.e., trees accepted by a tree automaton, into other _codes_.
|
|
211
|
+
The transformation is done by partial functions as defined in the language.
|
|
212
|
+
The non-recursive and tail-recursive (and even some non-tail recursive) functions can be compiled into deterministic finite (pushdown) transducers.
|
|
213
|
+
|
|
214
|
+
### AND-OR graphs for enconding and decoding codes
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## Linking with Other Languages
|
|
219
|
+
|
|
220
|
+
## Streams and Arrays (Map-Reduce)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { t, in_out } from './index.mjs';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import assert from 'assert';
|
|
4
|
+
|
|
5
|
+
// t(fs.readFileSync("./Examples/blowup.k").toString("utf8"), (annotated) => {
|
|
6
|
+
// // console.log(JSON.stringify(annotated,null,2));
|
|
7
|
+
// const {input,output} = in_out(annotated);
|
|
8
|
+
// console.log(input);
|
|
9
|
+
// console.log(output);
|
|
10
|
+
// assert.equal(output.pattern, '<...>');
|
|
11
|
+
// assert.equal(input.pattern, '(...)');
|
|
12
|
+
// console.log("OK");
|
|
13
|
+
// });
|
|
14
|
+
|
|
15
|
+
t(`
|
|
16
|
+
B = {{} u} A;
|
|
17
|
+
A = < .a, .b A, .c {() z} A, B >;
|
|
18
|
+
A
|
|
19
|
+
`, (annotated) => {
|
|
20
|
+
// console.log(JSON.stringify(annotated,null,2));
|
|
21
|
+
const {input,output} = in_out(annotated);
|
|
22
|
+
console.log(input);
|
|
23
|
+
console.log(output);
|
|
24
|
+
assert.equal(output.type, annotated.representatives["bnat"]);
|
|
25
|
+
assert.equal(input.type, annotated.representatives["pair"]);
|
|
26
|
+
console.log("OK");
|
|
27
|
+
});
|
|
@@ -3,8 +3,10 @@ import fs from 'node:fs';
|
|
|
3
3
|
import assert from 'assert';
|
|
4
4
|
|
|
5
5
|
t(fs.readFileSync("./Examples/bnat-patterns.k").toString("utf8"), (annotated) => {
|
|
6
|
+
// console.log(JSON.stringify(annotated,null,2));
|
|
6
7
|
const {input,output} = in_out(annotated);
|
|
7
|
-
|
|
8
|
-
assert.equal(
|
|
8
|
+
// console.log(output);
|
|
9
|
+
assert.equal(output.type, annotated.representatives["bnat"]);
|
|
10
|
+
assert.equal(input.type, annotated.representatives["pair"]);
|
|
9
11
|
console.log("OK");
|
|
10
12
|
});
|
|
@@ -9,7 +9,32 @@ $b {1 one, () two, () c} .one
|
|
|
9
9
|
|
|
10
10
|
`, (annotated) => {
|
|
11
11
|
const {input,output} = in_out(annotated);
|
|
12
|
-
|
|
13
|
-
assert.equal(
|
|
12
|
+
// console.log({input,output});
|
|
13
|
+
assert.equal(input.type, annotated.representatives["b"]);
|
|
14
|
+
assert.equal(output.type, "int");
|
|
14
15
|
console.log("OK");
|
|
15
|
-
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
t(`
|
|
19
|
+
$a = < int true, bool false > ;
|
|
20
|
+
$a .true
|
|
21
|
+
`, (annotated) => {
|
|
22
|
+
const {input,output} = in_out(annotated);
|
|
23
|
+
console.log({input,output});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
t(`
|
|
27
|
+
x = .x;
|
|
28
|
+
xy = x.y;
|
|
29
|
+
f = {
|
|
30
|
+
{ {5 y} x} xy i,
|
|
31
|
+
{ {true y} x} xy b
|
|
32
|
+
};
|
|
33
|
+
f .b
|
|
34
|
+
`, (annotated) => {
|
|
35
|
+
const {input,output} = in_out(annotated);
|
|
36
|
+
console.log({input,output});
|
|
37
|
+
assert.equal(input.pattern, '(...)');
|
|
38
|
+
assert.equal(output.type, "bool");
|
|
39
|
+
console.log("OK");
|
|
40
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { annotate } from "../index.mjs";
|
|
2
2
|
|
|
3
3
|
function in_out(annotated) {
|
|
4
|
-
const [input,output] = annotated.rels.__main__
|
|
5
|
-
annotated.
|
|
4
|
+
const [input,output] = annotated.rels.__main__.def.patterns.map( (pat) =>
|
|
5
|
+
annotated.rels.__main__.typePatternGraph.get_pattern(pat)
|
|
6
6
|
);
|
|
7
7
|
return { input, output };
|
|
8
8
|
}
|
|
@@ -4,7 +4,7 @@ import assert from 'assert';
|
|
|
4
4
|
|
|
5
5
|
t(fs.readFileSync("./Examples/nat.k").toString("utf8"), (annotated) => {
|
|
6
6
|
const {input,output} = in_out(annotated);
|
|
7
|
-
assert.equal(output.
|
|
8
|
-
assert.equal(input.
|
|
7
|
+
assert.equal(output.type, annotated.representatives["nat"]);
|
|
8
|
+
assert.equal(input.type, annotated.representatives["nat"]);
|
|
9
9
|
console.log("OK");
|
|
10
10
|
});
|
|
@@ -8,7 +8,7 @@ t(`$b = < {} true, {} false > ;
|
|
|
8
8
|
not not
|
|
9
9
|
`, (annotated) => {
|
|
10
10
|
const {input,output} = in_out(annotated);
|
|
11
|
-
assert.equal(input.
|
|
12
|
-
assert.equal(output.
|
|
11
|
+
assert.equal(input.type, annotated.representatives["b"]);
|
|
12
|
+
assert.equal(output.type, annotated.representatives["b"]);
|
|
13
13
|
console.log("OK");
|
|
14
14
|
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { t, in_out } from './index.mjs';
|
|
2
2
|
import assert from 'assert';
|
|
3
|
+
import hash from "../hash.mjs";
|
|
4
|
+
|
|
5
|
+
const unitCode = hash('$C0={};');
|
|
3
6
|
|
|
4
7
|
t('{}', (annotated) => {
|
|
5
8
|
const {input,output} = in_out(annotated);
|
|
6
|
-
assert.equal(input.
|
|
7
|
-
assert.equal(output.
|
|
9
|
+
assert.equal(input.pattern, '(...)');
|
|
10
|
+
assert.equal(output.type, unitCode);
|
|
8
11
|
console.log("OK");
|
|
9
12
|
});
|
|
10
13
|
|
|
@@ -12,10 +15,11 @@ t(`
|
|
|
12
15
|
$b = < {} true, {} false > ;
|
|
13
16
|
$pair = { b one, b two } ;
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
{() one, () two} $pair
|
|
16
19
|
`, (annotated) => {
|
|
17
20
|
const {input,output} = in_out(annotated);
|
|
18
|
-
|
|
19
|
-
assert.equal(
|
|
21
|
+
|
|
22
|
+
assert.equal(input.type, annotated.representatives["b"]);
|
|
23
|
+
assert.equal(output.type, annotated.representatives["pair"]);
|
|
20
24
|
console.log("OK");
|
|
21
25
|
});
|
|
@@ -8,7 +8,7 @@ t(`
|
|
|
8
8
|
rlz
|
|
9
9
|
`, (annotated) => {
|
|
10
10
|
const {input,output} = in_out(annotated);
|
|
11
|
-
assert.equal(output.
|
|
12
|
-
assert.equal(input.
|
|
11
|
+
assert.equal(output.type, annotated.representatives["nat"]);
|
|
12
|
+
assert.equal(input.type, annotated.representatives["nat"]);
|
|
13
13
|
console.log("OK");
|
|
14
14
|
});
|
|
@@ -3,26 +3,20 @@ import assert from 'assert';
|
|
|
3
3
|
|
|
4
4
|
t('[]', (annotated) => {
|
|
5
5
|
const {input,output} = in_out(annotated);
|
|
6
|
-
assert.equal(input.
|
|
7
|
-
assert.equal(output.
|
|
6
|
+
assert.equal(input.pattern, '(...)');
|
|
7
|
+
assert.equal(output.pattern, "[]");
|
|
8
8
|
console.log("OK");
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
t(
|
|
11
|
+
t("$int [ $int true, false ] .0", (annotated) => {
|
|
12
12
|
const {input,output} = in_out(annotated);
|
|
13
|
-
assert.equal(input.
|
|
14
|
-
assert.equal(output.type, "
|
|
13
|
+
assert.equal(input.type, "int");
|
|
14
|
+
assert.equal(output.type, "bool");
|
|
15
15
|
console.log("OK");
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
t(`$int [ $int true,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
assert.equal(output.code, "bool");
|
|
22
|
-
console.log("OK");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// assert.throws(() => t(`$int [ $int true, 12 ]`), /Error/);
|
|
26
|
-
// console.log("OK");
|
|
18
|
+
assert.throws(() => t(`$int [ $int true, 12 ]`), /Error/);
|
|
19
|
+
console.log("The both errors are expected...");
|
|
20
|
+
console.log("OK");
|
|
27
21
|
|
|
28
22
|
// t(`[]`);
|
package/Graph.mjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
class Graph {
|
|
2
|
+
constructor(edges, vertices = {}) {
|
|
3
|
+
this.edges = edges;
|
|
4
|
+
this.vertices = vertices;
|
|
5
|
+
for (const dir of ['src', 'dst']) {
|
|
6
|
+
this[dir] = {};
|
|
7
|
+
for (let i=0; i < edges.length; i++) {
|
|
8
|
+
const e = edges[i];
|
|
9
|
+
if (vertices[e[dir]] == undefined) {
|
|
10
|
+
vertices[e[dir]] = {
|
|
11
|
+
_rem: "discovered"
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (this[dir][e[dir]] == null) {
|
|
15
|
+
this[dir][e[dir]] = [];
|
|
16
|
+
}
|
|
17
|
+
this[dir][e[dir]].push(i);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sccs(graph) {
|
|
24
|
+
const stack = [];
|
|
25
|
+
const disc = {};
|
|
26
|
+
const low = {};
|
|
27
|
+
const inStack = {};
|
|
28
|
+
const sccs = [];
|
|
29
|
+
let time = 0;
|
|
30
|
+
|
|
31
|
+
const dfs = (u) => {
|
|
32
|
+
disc[u] = low[u] = time++;
|
|
33
|
+
stack.push(u);
|
|
34
|
+
inStack[u] = true;
|
|
35
|
+
|
|
36
|
+
for (const e of graph.src[u] || []) {
|
|
37
|
+
const v = graph.edges[e].dst;
|
|
38
|
+
if (disc[v] == undefined) {
|
|
39
|
+
dfs(v);
|
|
40
|
+
low[u] = Math.min(low[u], low[v]);
|
|
41
|
+
} else if (inStack[v]) {
|
|
42
|
+
low[u] = Math.min(low[u], disc[v]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (low[u] === disc[u]) {
|
|
47
|
+
const scc = [];
|
|
48
|
+
let w;
|
|
49
|
+
do {
|
|
50
|
+
w = stack.pop();
|
|
51
|
+
inStack[w] = false;
|
|
52
|
+
scc.push(w);
|
|
53
|
+
} while (w !== u);
|
|
54
|
+
sccs.push(scc);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
for (const x in graph.vertices) {
|
|
59
|
+
if (disc[x] == undefined) {
|
|
60
|
+
dfs(x);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return sccs;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function topoOrder(graph){
|
|
67
|
+
const starts = [];
|
|
68
|
+
const result = [];
|
|
69
|
+
const inDegree = {};
|
|
70
|
+
for (const v in graph.vertices) {
|
|
71
|
+
const degree = graph.dst[v]?.length || 0;
|
|
72
|
+
if (degree == 0) {
|
|
73
|
+
starts.push(v);
|
|
74
|
+
} else {
|
|
75
|
+
inDegree[v] = degree;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let x;
|
|
80
|
+
while (x = starts.pop()) {
|
|
81
|
+
result.push(x);
|
|
82
|
+
const outs = graph.src[x] || [];
|
|
83
|
+
for (const e of outs) {
|
|
84
|
+
const y = graph.edges[e].dst;
|
|
85
|
+
inDegree[y]--;
|
|
86
|
+
if (inDegree[y] == 0) {
|
|
87
|
+
starts.push(y);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { Graph, sccs, topoOrder };
|
|
95
|
+
export default { Graph, sccs, topoOrder };
|