@fraczak/k 4.0.4 → 5.0.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/BIG_PICTURE.md +106 -48
- package/Code-derivation-tests/bnat.mjs +1 -1
- package/TODO.md +18 -0
- package/TYPING.md +2 -2
- package/export.mjs +185 -0
- package/index.mjs +3 -15
- package/package.json +1 -1
- package/parser.jison +1 -1
- package/parser.mjs +1 -1
- package/patterns.mjs +25 -11
- package/pretty.mjs +26 -41
- package/repl.mjs +50 -27
- package/run.mjs +2 -0
- package/k-repl.cast~ +0 -1939
package/BIG_PICTURE.md
CHANGED
|
@@ -14,19 +14,24 @@ Formally, a _type_ is a (subclass of) finite tree automata. The type defines a s
|
|
|
14
14
|
which corresponds to the set of trees accepted by the automaton.
|
|
15
15
|
Therefore, every type admits a unique normal form in terms of its minimal deterministic tree automaton.
|
|
16
16
|
|
|
17
|
-
The set of all types can be seen as the universal deterministic (infinite) tree automaton.
|
|
18
|
-
Every node of that automaton defines a type; only a finite set of nodes are reachable from
|
|
19
|
-
any node.
|
|
20
|
-
|
|
21
17
|
Types are defined by two constructs:
|
|
22
18
|
|
|
23
19
|
1. Product
|
|
24
20
|
2. Union (also called tagged or disjoint union)
|
|
25
21
|
|
|
22
|
+
```bnf
|
|
23
|
+
type ::= name | '{' type_label_list '}' | '<' type_label_list '>'
|
|
24
|
+
type_label_list ::= /* empty */ | ( type label ',' )* type label
|
|
25
|
+
name ::= IDENTIFIER
|
|
26
|
+
label ::= STRING
|
|
27
|
+
```
|
|
28
|
+
|
|
26
29
|
Given a finite (possibly empty) set of types `t1, t2, ..., tn` and a set of pairwise different tags (labels) `l1, l2, ..., ln`,
|
|
27
30
|
|
|
28
|
-
- by `{ t1 l1, t2 l2, ..., tn ln }`, we define a _product type_ with exactly `n`
|
|
29
|
-
|
|
31
|
+
- by `{ t1 l1, t2 l2, ..., tn ln }`, we define a _product type_ with exactly `n`
|
|
32
|
+
_projection_ functions: `.l1`, `.l2`, `...`, `.ln`.
|
|
33
|
+
Each projection function is mapping a value of type `{t1 l1, t2 l2, ..., tn ln}`
|
|
34
|
+
into a value of type `t1`, `t2`, `...`, `tn`, respectively.
|
|
30
35
|
- by `< t1 l1, t2 l2, ..., tn ln >`, we define a _union type_ with exactly `n` projection functions `.l1`, `.l2`, `...`, `.ln`,
|
|
31
36
|
mapping a value of type `<t1 l1, t2 l2, ..., tn ln>` into a value of type `t1`, `t2`, `...`, `tn`, respectively.
|
|
32
37
|
|
|
@@ -38,11 +43,37 @@ Some properties of these types:
|
|
|
38
43
|
|
|
39
44
|
1. Singleton product and singleton union types, `{ t label }` and `< t label >`, are equivalent as they have
|
|
40
45
|
the same set of values (and the same projection function `.label`). That's why, we not allow singleton product nor
|
|
41
|
-
singleton union types in the syntax of the language. The other benefit is that we will be able to use the same
|
|
42
|
-
for values of both product and union types, interpretting every singleton product-like value, e.g., `{ a_value label }`,
|
|
46
|
+
singleton union types in the syntax of the language. The other benefit is that we will be able to use the same syntax
|
|
47
|
+
for writing out values of both product and union types, interpretting every singleton product-like value, e.g., `{ a_value label }`,
|
|
43
48
|
as a union type value (a variant).
|
|
44
|
-
2. The empty product type `{}
|
|
45
|
-
|
|
49
|
+
2. The empty product type, `{}`, has a single value, called the _unit_
|
|
50
|
+
and also denoted by `{}`, which has no fields.
|
|
51
|
+
3. The empty union type, `<>`, has no values.
|
|
52
|
+
|
|
53
|
+
### Filters (patterns)
|
|
54
|
+
|
|
55
|
+
A _filter_ is a way of representing a _type class_, i.e, a set of types sharing some struture.
|
|
56
|
+
For example, if I wanted to refer to all product types that have a field `f` of type `T`,
|
|
57
|
+
I would write a pattern: `?{ T f, ... }`. In general, a filter is defined by:
|
|
58
|
+
|
|
59
|
+
```bnf
|
|
60
|
+
filter ::= name | '$' type_expr | '{' filter_label_list '}'
|
|
61
|
+
| '<' filter_label_list '>' | '(' filter_label_list ')'
|
|
62
|
+
| filter '=' name
|
|
63
|
+
filter_label_list ::= /* empty */ | '...'
|
|
64
|
+
| ( filter label ',' ) * filter label
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
|
|
69
|
+
```k-repl
|
|
70
|
+
X -- any type
|
|
71
|
+
( ... ) -- any type
|
|
72
|
+
{ ... } -- any product type
|
|
73
|
+
( X x, X y ) -- any type with two fields of the same type
|
|
74
|
+
< X x, ... > = X -- a (recursive) type, 'X', with variant 'x'
|
|
75
|
+
-- of the same type 'X'
|
|
76
|
+
```
|
|
46
77
|
|
|
47
78
|
### Partial functions
|
|
48
79
|
|
|
@@ -50,16 +81,19 @@ A _partial function_ is a function that is not defined for every value of its do
|
|
|
50
81
|
|
|
51
82
|
There are three ways of combining partial functions:
|
|
52
83
|
|
|
53
|
-
1. __Composition__: we write a composition of `f` and `g` as `(f g)`,
|
|
84
|
+
1. __Composition__: we write a composition of `f` and `g` as `(f g)`,
|
|
85
|
+
meaning that if `f` is defined on value `x` producing `y`,
|
|
54
86
|
and `g` is defined on value `y` producing `z`, then `(f g)` is defined on value `x` producing `z`.
|
|
55
87
|
2. __Union__: we write `< f1, f2 >` for a union of two functions `f1` and `f2`, meaning that
|
|
56
88
|
if `f1` is defined on value `x` producing `y`, then `< f1, f2 >`
|
|
57
89
|
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`,
|
|
59
|
-
are both defined on value `x` producing `y1` and `y2`,
|
|
60
|
-
|
|
90
|
+
3. __Product__: we write `{f1 lab1, f2 lab2 }` for a product of two functions `f1` and `f2`,
|
|
91
|
+
meaning that if `f1` and `f2` are both defined on value `x` producing `y1` and `y2`,
|
|
92
|
+
respectively, then `{f1 lab1, f2 lab2 }` is defined on value `x` producing
|
|
93
|
+
`{y1 lab1, y2 lab2}`.
|
|
94
|
+
Otherwise, `{f1 lab1, f2 lab2 }` is not defined for `x`.
|
|
61
95
|
|
|
62
|
-
Actually,
|
|
96
|
+
Actually, composition, union, and product combine any number of partial functions, not only two. For example,
|
|
63
97
|
empty composition, `()`, defines the identity function.
|
|
64
98
|
|
|
65
99
|
### Program
|
|
@@ -70,68 +104,66 @@ which is the partial function the program defines.
|
|
|
70
104
|
The language syntax is defined by:
|
|
71
105
|
|
|
72
106
|
```bnf
|
|
73
|
-
program ::= (type_definition | function_definition)
|
|
74
|
-
|
|
107
|
+
program ::= (type_definition | function_definition)* expression
|
|
75
108
|
type_definition ::= '$' type_name '=' type ';'
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
function_definition ::= function_name '=' expression ';'
|
|
81
|
-
expression ::= projection | typing | composition | union | product | function_name
|
|
82
|
-
projection ::= '.' label_name
|
|
83
|
-
typing ::= '$' tape_expression
|
|
109
|
+
function_definition ::= name '=' expression ';'
|
|
110
|
+
expression ::= name | '$' type | '?' filter
|
|
111
|
+
| projection | composition | union | product
|
|
112
|
+
projection ::= '.' label
|
|
84
113
|
composition ::= '(' expression * ')'
|
|
85
114
|
union ::= '<' (expression ',') + expression '>'
|
|
86
|
-
product ::= '{'
|
|
87
|
-
|
|
115
|
+
product ::= '{' expr_label_list '}'
|
|
116
|
+
expr_label_list ::= /* empty */
|
|
117
|
+
| (expression label_name ',')* expression label
|
|
88
118
|
```
|
|
89
119
|
|
|
90
|
-
We assume that
|
|
120
|
+
We assume that in a `kernel-core` program the names (for types, functions, and labels) are identifiers (strings) such that:
|
|
91
121
|
|
|
92
122
|
1. All defined type names are distinct.
|
|
93
123
|
2. All defined function names are distinct.
|
|
94
124
|
3. All label names within a type definition or a `product` expression are locally distinct.
|
|
95
125
|
|
|
96
|
-
|
|
97
|
-
function defined only for the values of the corresponding
|
|
126
|
+
`Filter` or `type` expressions act as a type annotation, i.e., an identity
|
|
127
|
+
function defined only for the values of the corresponding types.
|
|
98
128
|
|
|
99
129
|
That's it.
|
|
130
|
+
|
|
100
131
|
No `builtin` types, no `if` statement, no `loop`, no `throw`, no _closure_, no _annotations_, and no macros.
|
|
101
|
-
Just
|
|
132
|
+
Just _types_, _filters_, and _functions_.
|
|
102
133
|
|
|
103
134
|
### Normalization
|
|
104
135
|
|
|
105
136
|
All type names are replaced by the hash of their canonical representation.
|
|
106
137
|
|
|
107
|
-
We
|
|
108
|
-
Obviously,
|
|
138
|
+
We replace the function name by the hash of the normalized definition of the function as provided by the program defining the function.
|
|
139
|
+
Obviously, a function may have many different definitions so the hash-based names are not canonical. By using hash-based function names we avoid the problem of name clashes when bringing together many `kernel-core` programs.
|
|
109
140
|
|
|
110
|
-
This approach solves the problem of modules
|
|
141
|
+
This approach solves the problem of modules, imports, etc., and opens the door to a universal
|
|
111
142
|
registry of types (schemas) and functions.
|
|
112
143
|
|
|
113
144
|
## Typing and Polymorphism (codes and filters/patterns)
|
|
114
145
|
|
|
115
|
-
Abstract Syntax Tree of a program
|
|
146
|
+
Abstract Syntax Tree of a program consists of a dictionary of _type definitions_,
|
|
116
147
|
a dictionary of _function definitions_, and the final _main expression_.
|
|
117
148
|
|
|
118
149
|
In principle, the initial form of the Abstract Syntax Tree, _raw_ AST, is enough to evaluate
|
|
119
150
|
the program on an input.
|
|
120
151
|
|
|
121
|
-
Our objective however is to compute
|
|
122
|
-
representations for functions, so they could be
|
|
152
|
+
Our objective however is to compute canonical representations for types and normalized
|
|
153
|
+
representations for functions, so they could be reused.
|
|
123
154
|
|
|
124
155
|
The normalization steps are:
|
|
125
156
|
|
|
126
157
|
1. Build the _type graph_ from all types used by the program, i.e., used in _type definitions_
|
|
127
158
|
as well as type expressions used in _function definitions_ and the _main expression_.
|
|
128
|
-
2. Annotate the expressions with
|
|
129
|
-
3. Turn the _singleton_
|
|
130
|
-
4. Normalize the function definitions by rewriting them as the fix-point of a single equation.
|
|
159
|
+
2. Annotate the expressions with filters; every node of the AST is annotated with a pair of filters.
|
|
160
|
+
3. Turn the _singleton_ filters into types and add them to the _type graph_. Go to (2) unless no change.
|
|
131
161
|
|
|
132
|
-
###
|
|
162
|
+
### Examples
|
|
133
163
|
|
|
134
|
-
|
|
164
|
+
#### Example 1
|
|
165
|
+
|
|
166
|
+
```k-repl
|
|
135
167
|
$ bit = < {} o, {} i >;
|
|
136
168
|
bit0 = {{} o} $bit;
|
|
137
169
|
bit1 = {{} i} $bit;
|
|
@@ -169,7 +201,7 @@ and a type `byte` as a product, for simplicity, of four `bit`s.
|
|
|
169
201
|
Functions `bit0`, `bit1`, and `zero` are "constant polymorphic functions", meaning:
|
|
170
202
|
|
|
171
203
|
- constant: if defined, they always return exactly the same value;
|
|
172
|
-
- polymorphic: they are defined for more than one pair of input and output
|
|
204
|
+
- polymorphic: they are defined for more than one pair of input and output types: functions
|
|
173
205
|
`bit0` and `bit1` are of type `?X -> $bit`, and `zero` is of type `?X -> $byte`, where `?X`
|
|
174
206
|
denotes an unconstrained type pattern, also denoted as `?(...)`.
|
|
175
207
|
|
|
@@ -186,15 +218,42 @@ with following constraints:
|
|
|
186
218
|
> `?( V i, ...)`;
|
|
187
219
|
- `V` is unconstrained, denoted by `(...)`;
|
|
188
220
|
|
|
189
|
-
We
|
|
221
|
+
We can write it as: `?($byte byte, ((...) i, ...) overflown, ...)`.
|
|
190
222
|
|
|
191
223
|
The target type `${ byte byte, bit overflown }` was derived from pattern `?{ $byte byte, $bit overflown }`.
|
|
192
|
-
Such a
|
|
224
|
+
Such a pattern is called _singleton pattern_, as only one type fits the pattern.
|
|
225
|
+
|
|
226
|
+
#### Example 2
|
|
227
|
+
|
|
228
|
+
Polymorphic list functions:
|
|
229
|
+
|
|
230
|
+
```k-repl
|
|
231
|
+
list? = ?< {} nil, {X car, Y cdr} cons > = Y;
|
|
232
|
+
nil = {{} nil} list?;
|
|
233
|
+
singleton = {{() car, nil cdr} cons} list?;
|
|
234
|
+
cons = {{.car car, .cdr cdr} cons} list?;
|
|
235
|
+
nil? = list? .nil nil;
|
|
236
|
+
car = list? .cons .car;
|
|
237
|
+
cdr = list? .cons .cdr;
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
For example, the filter annotation for `car` is:
|
|
241
|
+
|
|
242
|
+
```k-repl
|
|
243
|
+
--R car
|
|
244
|
+
{
|
|
245
|
+
name: 'yRXGSJrIWCuKV',
|
|
246
|
+
type: '?<{X1 car, X0 cdr} cons, $KL nil>=X0 --> ?X1',
|
|
247
|
+
def: '?<{X1 car, X0 cdr} cons, $KL nil>=X0 .cons .car ?X1'
|
|
248
|
+
}
|
|
249
|
+
--C KL
|
|
250
|
+
$ KL = {}; -- $C0={};
|
|
251
|
+
```
|
|
193
252
|
|
|
194
253
|
## Universal Schema Registry
|
|
195
254
|
|
|
196
255
|
Since the normalization process for types is fast and deterministic, we can build a universal
|
|
197
|
-
schema registry that will store all invented types.
|
|
256
|
+
schema registry that will store all invented types.
|
|
198
257
|
The registry will be a key-value store, where the key is the hash of the normalized type,
|
|
199
258
|
and the value is the normalized type itself.
|
|
200
259
|
|
|
@@ -203,8 +262,7 @@ definition and stored in the similar key-value store.
|
|
|
203
262
|
|
|
204
263
|
Non-polymorphic functions can be easily indexed by the hashes of their input and output types
|
|
205
264
|
so that we can quickly find the function we need.
|
|
206
|
-
|
|
207
|
-
whose input and output type patterns fit given types.
|
|
265
|
+
Indexing polymorphic functions seems complicated (TODO).
|
|
208
266
|
|
|
209
267
|
## Serialization and Compilation
|
|
210
268
|
|
|
@@ -5,7 +5,7 @@ import assert from 'assert';
|
|
|
5
5
|
t(fs.readFileSync("./Examples/bnat-patterns.k").toString("utf8"), (annotated) => {
|
|
6
6
|
// console.log(JSON.stringify(annotated,null,2));
|
|
7
7
|
const {input,output} = in_out(annotated);
|
|
8
|
-
|
|
8
|
+
console.log(output);
|
|
9
9
|
assert.equal(output.type, annotated.representatives["bnat"]);
|
|
10
10
|
assert.equal(input.type, annotated.representatives["pair"]);
|
|
11
11
|
console.log("OK");
|
package/TODO.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# To do list for canonical names
|
|
2
|
+
|
|
3
|
+
- [ ] Write a script to search for conflicting names
|
|
4
|
+
- [ ] Export a relation. i.e., rewrite it as a list of all
|
|
5
|
+
dependency definitions using canonical names
|
|
6
|
+
- [ ] Find a way of adding 'comments' or some way of 'tagging' the definitions
|
|
7
|
+
- [X] The definitions should be simplied by removing useless filters
|
|
8
|
+
- [X] Definitions, which are just aliases to non built-in rels should not introduce a new canonical name
|
|
9
|
+
- [ ] Can we inline some references w/o changing the canonical name?
|
|
10
|
+
|
|
11
|
+
## Simplification rules
|
|
12
|
+
|
|
13
|
+
It seems to me that after calculating input and output patterns, all
|
|
14
|
+
filters and code expressions could be replaced by just input and output
|
|
15
|
+
filters corresponding to the patterns. [DONE]
|
|
16
|
+
|
|
17
|
+
The filer variables have to be renamed for normalization if they take part of
|
|
18
|
+
the canonical name calculation. [DONE]
|
package/TYPING.md
CHANGED
|
@@ -360,6 +360,6 @@ Repeat the procedure until the type pattern graph is not changing anymore.
|
|
|
360
360
|
- [X] Redefine built-in CONS and SNOC to allign with no support for non-homogeneous lists
|
|
361
361
|
- [X] Integrate filters into typePatters (execution is not affected)
|
|
362
362
|
- [/] better type error repporting - the info exists in the pattern parent tree ... in progress
|
|
363
|
-
- [
|
|
363
|
+
- [X] Introduce a space/time bounds for type derivation
|
|
364
364
|
- [ ] Check for loops, e.g., `$x = < x f, ...>`, `$x = [x]`, `$x = {x f, ...}` -- this
|
|
365
|
-
looks a little arbitrary, but maybe this is actually an important part
|
|
365
|
+
looks a little arbitrary, but maybe this is actually an important part
|
package/export.mjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
|
|
2
|
+
import { prettyRel, patterns2filters } from "./pretty.mjs";
|
|
3
|
+
import { hash } from "./hash.mjs";
|
|
4
|
+
|
|
5
|
+
function simplifyRel(relDef,rels) {
|
|
6
|
+
// remove filters and codes
|
|
7
|
+
const prune = (rel) => {
|
|
8
|
+
const newRel = {...rel};
|
|
9
|
+
switch (rel.op) {
|
|
10
|
+
case "product":
|
|
11
|
+
newRel.product = rel.product.map(({label, exp}) => ({label, exp: prune(exp)}));
|
|
12
|
+
break;
|
|
13
|
+
case "union":
|
|
14
|
+
newRel.union = rel.union.map(exp => prune(exp));
|
|
15
|
+
break;
|
|
16
|
+
case "comp":
|
|
17
|
+
newRel.comp = rel.comp.reduce((list, exp) => {
|
|
18
|
+
const newExp = prune(exp);
|
|
19
|
+
if (newExp.op != "identity")
|
|
20
|
+
list.push(newExp);
|
|
21
|
+
return list;
|
|
22
|
+
},[]);
|
|
23
|
+
if (newRel.comp.length == 0)
|
|
24
|
+
newRel.op = "identity";
|
|
25
|
+
break;
|
|
26
|
+
case "vector":
|
|
27
|
+
newRel.vector = rel.vector.map(exp => prune(exp));
|
|
28
|
+
break;
|
|
29
|
+
case "caret":
|
|
30
|
+
newRel.caret = prune(rel.caret);
|
|
31
|
+
break;
|
|
32
|
+
case 'code':
|
|
33
|
+
case 'filter':
|
|
34
|
+
newRel.op = "identity";
|
|
35
|
+
break;
|
|
36
|
+
case 'ref':
|
|
37
|
+
// console.log("ref", rel.ref, rels[rel.ref]);
|
|
38
|
+
if (["filter","code","identity"].includes(rels[rel.ref]?.def.op)){
|
|
39
|
+
newRel.op = "identity";
|
|
40
|
+
};
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
break;
|
|
44
|
+
};
|
|
45
|
+
return newRel;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const rel = relDef.def;
|
|
49
|
+
const filters = patterns2filters(relDef.typePatternGraph, ...rel.patterns).map( filter =>
|
|
50
|
+
({op: "filter", filter: filter}));
|
|
51
|
+
const newRel = prune(rel);
|
|
52
|
+
let resultRel = {...newRel};
|
|
53
|
+
if (newRel.op == "identity")
|
|
54
|
+
return {...resultRel, ...filters[0]};
|
|
55
|
+
|
|
56
|
+
if (newRel.op == "comp") {
|
|
57
|
+
resultRel.comp = [ filters[0], ...newRel.comp, filters[1] ];
|
|
58
|
+
return resultRel;
|
|
59
|
+
}
|
|
60
|
+
return {...resultRel,
|
|
61
|
+
op: "comp",
|
|
62
|
+
comp: [filters[0], newRel, filters[1] ]
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
function theID(alias, rel, scc, name) {
|
|
68
|
+
const sccNames = new Set(scc);
|
|
69
|
+
const auxNames = {[name]: "X0"};
|
|
70
|
+
// rename and remove filters and codes
|
|
71
|
+
const reNameX = (rel) => {
|
|
72
|
+
const newRel = {...rel};
|
|
73
|
+
switch (rel.op) {
|
|
74
|
+
case "product":
|
|
75
|
+
newRel.product = rel.product.map(({label, exp}) => ({label, exp: reNameX(exp)}));
|
|
76
|
+
break;
|
|
77
|
+
case "union":
|
|
78
|
+
newRel.union = rel.union.map(exp => reNameX(exp));
|
|
79
|
+
break;
|
|
80
|
+
case "comp":
|
|
81
|
+
newRel.comp = rel.comp.map(exp => reNameX(exp));
|
|
82
|
+
break;
|
|
83
|
+
case "vector":
|
|
84
|
+
newRel.vector = rel.vector.map(exp => reNameX(exp));
|
|
85
|
+
break;
|
|
86
|
+
case "caret":
|
|
87
|
+
newRel.caret = reNameX(rel.caret);
|
|
88
|
+
break;
|
|
89
|
+
case "ref": {
|
|
90
|
+
const n = rel.ref;
|
|
91
|
+
if (alias[n] != undefined) {
|
|
92
|
+
newRel.ref = alias[n];
|
|
93
|
+
} else if (auxNames[n] != undefined) {
|
|
94
|
+
newRel.ref = auxNames[n];
|
|
95
|
+
} else if (sccNames.has(n)) {
|
|
96
|
+
auxNames[n] = `X${Object.keys(auxNames).length}`;
|
|
97
|
+
newRel.ref = auxNames[n];
|
|
98
|
+
};
|
|
99
|
+
}; break;
|
|
100
|
+
default:
|
|
101
|
+
break;
|
|
102
|
+
};
|
|
103
|
+
return newRel;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const newRel = reNameX(rel);
|
|
107
|
+
const resultRelStr = prettyRel(newRel);
|
|
108
|
+
// console.log(` ${name} = ${resultRelStr}`);
|
|
109
|
+
const newName = hash(resultRelStr);
|
|
110
|
+
return newName;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function assignCanonicalNames(scc, rels, relAlias) {
|
|
114
|
+
const newAlias = scc.reduce( (newAlias,relName) => {
|
|
115
|
+
const relDef = rels[relName];
|
|
116
|
+
if (relDef.def.op == "ref" && rels[relDef.def.ref] != undefined) {
|
|
117
|
+
// inlining if direct alias to a non built-in relation
|
|
118
|
+
// relDef.def = rels[relDef.def.ref]; // simplifyRel(rels[relDef.def.ref],rels);
|
|
119
|
+
rels[relName] = rels[relDef.def.ref];
|
|
120
|
+
} else {
|
|
121
|
+
relDef.def = simplifyRel(relDef,rels);
|
|
122
|
+
}
|
|
123
|
+
newAlias[relName] = theID(relAlias, rels[relName].def, scc, relName);
|
|
124
|
+
return newAlias;
|
|
125
|
+
}, {});
|
|
126
|
+
const newNames = [...new Set(Object.values(newAlias))];
|
|
127
|
+
// console.log(` --- SCC: {${scc.join(",")}} ---`);
|
|
128
|
+
|
|
129
|
+
const sccCanonicalName = newNames.sort().join(":");
|
|
130
|
+
for (let relName in newAlias) {
|
|
131
|
+
if (newNames.length > 1)
|
|
132
|
+
relAlias[relName] = hash(newAlias[relName]+":"+sccCanonicalName);
|
|
133
|
+
else
|
|
134
|
+
relAlias[relName] = newAlias[relName];
|
|
135
|
+
// console.log(` ${relName} -> ${relAlias[relName]}`);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function exportRelation(rels, alias, name) {
|
|
140
|
+
const queue = [name];
|
|
141
|
+
const inQueue = { [name]: true };
|
|
142
|
+
const result = {};
|
|
143
|
+
const reName = (rel) => {
|
|
144
|
+
const newRel = {...rel};
|
|
145
|
+
switch (rel.op) {
|
|
146
|
+
case "product":
|
|
147
|
+
newRel.product = rel.product.map(({label, exp}) => ({label, exp: reName(exp)}));
|
|
148
|
+
break;
|
|
149
|
+
case "union":
|
|
150
|
+
newRel.union = rel.union.map(exp => reName(exp));
|
|
151
|
+
break;
|
|
152
|
+
case "comp":
|
|
153
|
+
newRel.comp = rel.comp.map(exp => reName(exp));
|
|
154
|
+
break;
|
|
155
|
+
case "vector":
|
|
156
|
+
newRel.vector = rel.vector.map(exp => reName(exp));
|
|
157
|
+
break;
|
|
158
|
+
case "caret":
|
|
159
|
+
newRel.caret = reName(rel.caret);
|
|
160
|
+
break;
|
|
161
|
+
case "ref": {
|
|
162
|
+
const n = rel.ref;
|
|
163
|
+
if (alias[n] == undefined) // built-in
|
|
164
|
+
return newRel;
|
|
165
|
+
newRel.ref = alias[n];
|
|
166
|
+
if (! inQueue[n]) {
|
|
167
|
+
inQueue[n] = true;
|
|
168
|
+
queue.push(n);
|
|
169
|
+
}
|
|
170
|
+
}; break;
|
|
171
|
+
default:
|
|
172
|
+
break;
|
|
173
|
+
};
|
|
174
|
+
return newRel;
|
|
175
|
+
};
|
|
176
|
+
while (queue.length > 0 ) {
|
|
177
|
+
const name = queue.shift();
|
|
178
|
+
const canonicalName = alias[name];
|
|
179
|
+
result[canonicalName] = {...rels[name], def: reName(rels[name].def), name: name};
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export default { assignCanonicalNames, exportRelation };
|
|
185
|
+
export { assignCanonicalNames, exportRelation };
|
package/index.mjs
CHANGED
|
@@ -4,19 +4,7 @@ import run from "./run.mjs";
|
|
|
4
4
|
import t from "./codes.mjs";
|
|
5
5
|
|
|
6
6
|
function compile(script) {
|
|
7
|
-
|
|
8
|
-
run.defs = annotate(script);
|
|
9
|
-
} catch (e) {
|
|
10
|
-
console.error(e.message);
|
|
11
|
-
console.error("WARN: Recompiling without type reconciliation due to the type error above.");
|
|
12
|
-
const { defs, exp } = parse(script);
|
|
13
|
-
const { codes, representatives } = t.finalize(defs.codes);
|
|
14
|
-
run.defs = {
|
|
15
|
-
rels: {...defs.rels, "__main__": {def: exp}},
|
|
16
|
-
codes, representatives
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
}
|
|
7
|
+
run.defs = annotate(script);
|
|
20
8
|
return run.bind(null, run.defs.rels.__main__.def);
|
|
21
9
|
}
|
|
22
10
|
|
|
@@ -35,9 +23,9 @@ function annotate(script) {
|
|
|
35
23
|
const representatives = t.register(defs.codes);
|
|
36
24
|
const rels = {...defs.rels, "__main__": {def: exp}};
|
|
37
25
|
|
|
38
|
-
patterns(representatives, rels);
|
|
26
|
+
const relAlias = patterns(representatives, rels);
|
|
39
27
|
|
|
40
|
-
return {rels, representatives}
|
|
28
|
+
return {rels, representatives, relAlias};
|
|
41
29
|
}
|
|
42
30
|
annotate.doc = "Annotate all the script expressions with patterns";
|
|
43
31
|
|
package/package.json
CHANGED
package/parser.jison
CHANGED
|
@@ -43,7 +43,7 @@ function fromEscString(escString) {
|
|
|
43
43
|
|
|
44
44
|
%%
|
|
45
45
|
[/][*]([^*]*[*]+[^*/])*[^*]*[*]+[/] /* c-comment */
|
|
46
|
-
("//"|"#"|"%"|"--")[^\n]*
|
|
46
|
+
("//"|"#"|"%"|"--"|"\\")[^\n]* /* one line comment */
|
|
47
47
|
\s+ /* blanks */
|
|
48
48
|
"<" return 'LA';
|
|
49
49
|
"{" return 'LC';
|
package/parser.mjs
CHANGED
|
@@ -846,7 +846,7 @@ case 25:return 47;
|
|
|
846
846
|
break;
|
|
847
847
|
}
|
|
848
848
|
},
|
|
849
|
-
rules: [/^(?:[/][*]([^*]*[*]+[^*/])*[^*]*[*]+[/])/,/^(?:(
|
|
849
|
+
rules: [/^(?:[/][*]([^*]*[*]+[^*/])*[^*]*[*]+[/])/,/^(?:(\/\/|#|%|--|\\)[^\n]*)/,/^(?:\s+)/,/^(?:<)/,/^(?:\{)/,/^(?:\[)/,/^(?:\()/,/^(?:>)/,/^(?:\})/,/^(?:\])/,/^(?:\))/,/^(?:=)/,/^(?:\.\.\.)/,/^(?:\.)/,/^(?:,)/,/^(?:;)/,/^(?::)/,/^(?:\$)/,/^(?:\?)/,/^(?:\|)/,/^(?:\^)/,/^(?:"([^"\\]|\\(.|\n))*"|'([^'\\]|\\(.|\n))*')/,/^(?:[a-zA-Z_][a-zA-Z0-9_?!]*)/,/^(?:0|[-]?[1-9][0-9]*)/,/^(?:\+\+\+)/,/^(?:$)/],
|
|
850
850
|
conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25],"inclusive":true}}
|
|
851
851
|
});
|
|
852
852
|
return lexer;
|
package/patterns.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import hash from "./hash.mjs";
|
|
|
3
3
|
import { TypePatternGraph } from "./typing.mjs";
|
|
4
4
|
import { Graph, sccs, topoOrder } from "./Graph.mjs";
|
|
5
5
|
import codes from "./codes.mjs";
|
|
6
|
+
import { assignCanonicalNames } from "./export.mjs";
|
|
6
7
|
|
|
7
8
|
const unitCode = codes.unitCode;
|
|
8
9
|
|
|
@@ -97,10 +98,11 @@ function compactRel(relDef, name = "") {
|
|
|
97
98
|
|
|
98
99
|
function patterns(representatives, rels) {
|
|
99
100
|
// INPUT:
|
|
100
|
-
// codes: {"
|
|
101
|
-
// representatives:{"{}": "
|
|
101
|
+
// codes: {"KL": {"code": "product", "product": {}}, ...}
|
|
102
|
+
// representatives:{"{}": "KL", ...}
|
|
102
103
|
// rels: {"rlz": [{op: comp,...}, ...], ...}
|
|
103
104
|
|
|
105
|
+
const relAlias = {};
|
|
104
106
|
|
|
105
107
|
// 1 INITIALIZATION
|
|
106
108
|
// 1.1 initialize patternNodes and rels
|
|
@@ -405,7 +407,7 @@ function patterns(representatives, rels) {
|
|
|
405
407
|
rel.patterns = [rootDef.typePatternGraph.addNewNode(), rootDef.typePatternGraph.addNewNode()];
|
|
406
408
|
break;
|
|
407
409
|
default:
|
|
408
|
-
throw new Error(`No definition found for ${rel.ref}`);
|
|
410
|
+
throw new Error(`No definition found for '${rel.ref}'`);
|
|
409
411
|
}
|
|
410
412
|
}
|
|
411
413
|
|
|
@@ -416,7 +418,7 @@ function patterns(representatives, rels) {
|
|
|
416
418
|
for (const relName in rels) {
|
|
417
419
|
const rootDef = rels[relName];
|
|
418
420
|
rootDef.typePatternGraph = new TypePatternGraph();
|
|
419
|
-
rootDef.varRefs = []; // the list of non-
|
|
421
|
+
rootDef.varRefs = []; // the list of references to non-builtin rels as pointers to AST nodes
|
|
420
422
|
rootDef.patternVars = {};
|
|
421
423
|
augment(rootDef.def, rootDef);
|
|
422
424
|
delete rootDef.patternVars;
|
|
@@ -453,12 +455,16 @@ function patterns(representatives, rels) {
|
|
|
453
455
|
|
|
454
456
|
// 3. Topological sort of the DAG
|
|
455
457
|
|
|
456
|
-
const sccInOrder = topoOrder(DAG);
|
|
458
|
+
const sccInOrder = topoOrder(DAG).reverse();
|
|
457
459
|
|
|
458
460
|
// 4. For each strongly connected component C in a bottom-up order
|
|
461
|
+
|
|
462
|
+
// we will also generate the canonical representation of the relations in C and replace
|
|
463
|
+
// the name to the relation by the hash of the canonical representation
|
|
464
|
+
|
|
465
|
+
// console.log("----- SCC in order", sccInOrder);
|
|
459
466
|
|
|
460
|
-
for (const scc of sccInOrder
|
|
461
|
-
// console.log(`SCC: { ${DAGnodes[scc].scc} }`);
|
|
467
|
+
for (const scc of sccInOrder) {
|
|
462
468
|
|
|
463
469
|
const maxNumberOfIterations = 10;
|
|
464
470
|
|
|
@@ -497,9 +503,7 @@ function patterns(representatives, rels) {
|
|
|
497
503
|
throw e;
|
|
498
504
|
}
|
|
499
505
|
}
|
|
500
|
-
|
|
501
506
|
rels[relName] = compactRel(relDef,relName);
|
|
502
|
-
|
|
503
507
|
return relDefToString(rels[relName]);
|
|
504
508
|
}));
|
|
505
509
|
|
|
@@ -507,9 +511,19 @@ function patterns(representatives, rels) {
|
|
|
507
511
|
// console.log("SUCCESS!");
|
|
508
512
|
break;
|
|
509
513
|
}
|
|
510
|
-
} //
|
|
511
|
-
|
|
514
|
+
} // end of iteration loop
|
|
515
|
+
|
|
516
|
+
DAGnodes[scc].scc.forEach( relName => {
|
|
517
|
+
const relDef = rels[relName];
|
|
518
|
+
const canonical = relDefToString(relDef);
|
|
519
|
+
const h = hash(canonical);
|
|
520
|
+
relAlias[relName] = h;
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
assignCanonicalNames(DAGnodes[scc].scc, rels, relAlias);
|
|
524
|
+
|
|
512
525
|
}
|
|
526
|
+
return relAlias;
|
|
513
527
|
}
|
|
514
528
|
|
|
515
529
|
export default { patterns };
|