@fraczak/k 1.3.4 → 2.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/README.md +224 -44
- package/codes.mjs +103 -0
- package/index.mjs +32 -0
- package/k.mjs +79 -0
- package/package.json +6 -9
- package/parser.jison +1 -1
- package/{parser.js → parser.mjs} +9 -17
- package/pretty.mjs +95 -0
- package/repl.mjs +112 -0
- package/run.mjs +158 -0
- package/symbol-table.mjs +133 -0
- package/test.mjs +143 -0
- package/INTRO.md +0 -160
- package/codes.coffee +0 -81
- package/codes.js +0 -134
- package/index.coffee +0 -28
- package/index.js +0 -49
- package/k.coffee +0 -59
- package/k.js +0 -93
- package/k.sh +0 -94
- package/pretty.coffee +0 -74
- package/pretty.js +0 -106
- package/repl.coffee +0 -62
- package/repl.js +0 -101
- package/repl.sh +0 -102
- package/run.coffee +0 -117
- package/run.js +0 -229
- package/symbol-table.coffee +0 -90
- package/symbol-table.js +0 -165
- package/test.coffee +0 -68
- package/test.js +0 -161
package/README.md
CHANGED
|
@@ -4,36 +4,183 @@
|
|
|
4
4
|
|
|
5
5
|
From javascript:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import k from "@fraczak/k";
|
|
8
|
+
|
|
9
|
+
const fn = k.compile("<.name,.nom,'?'>");
|
|
9
10
|
console.log([{name:"x"},{nom:"y"},{}].map(fn));
|
|
10
11
|
// returns: [ "x", "y", "?" ]
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
defines a __partial function__, i.e., a function which may fail
|
|
14
|
-
for some input. `k`-expressions can be combined to build
|
|
15
|
-
other `k`-expressions in three ways:
|
|
13
|
+
---
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
## k - the way of building and manipulating JSON-like data
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
Technically, `k` is a notation for defining **first-order partial functions**.
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
An example of a _partial function_ is the _projection_, e.g., "`.toto`",
|
|
20
|
+
which maps an object to its property named `toto`, or it is not defined if
|
|
21
|
+
the property doesn't exist. E.g.,
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
1 .toto :
|
|
24
|
+
2 {"toto": 5, "titi": 10} --> 5
|
|
25
|
+
3 {"titi": 10} ... undefined // it is not a value!
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
> Note: The above 3 lines should be read as follows. A `k`-expression
|
|
28
|
+
> is printed in the first line (before "`:`"). The following lines
|
|
29
|
+
> are examples of the function defined by the `k`-expression applayed
|
|
30
|
+
> to JSON values (first part of each line). If the function for the
|
|
31
|
+
> value is defined, then the result is printed after "`-->`" (line 2 in
|
|
32
|
+
> the above example). If the function is not defined for the value,
|
|
33
|
+
> then "`... undefined`" is printed (line 3).
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
---
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
### Combining "partial functions"
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
There are three ways of combining functions:
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
1. **composition**: `(f1 f2 ...)`, e.g. `(.toto .titi)` extracts
|
|
42
|
+
nested field.
|
|
43
|
+
|
|
44
|
+
(.toto .titi) :
|
|
45
|
+
{"toto": {"titi": 10}} --> 10
|
|
46
|
+
{"toto": 10 } ... undefined
|
|
47
|
+
{} ... undefined
|
|
48
|
+
|
|
49
|
+
2. **merge**: `< f1, f2,... >`, e.g., `<.toto, .titi>` extracts field
|
|
50
|
+
`toto` if present; otherwise extracts `titi`.
|
|
51
|
+
|
|
52
|
+
<.toto, .titi> :
|
|
53
|
+
{"toto": 5, "titi": 10} --> 5
|
|
54
|
+
{"titi": 10 } --> 10
|
|
55
|
+
{} ... undefined
|
|
56
|
+
|
|
57
|
+
3. **product**: `{ f1 label1, f2 label2, ...}`, e.g., `{.toto TOTO,
|
|
58
|
+
.titi TITI}` extracts two fields and builds a record out of them.
|
|
59
|
+
|
|
60
|
+
{.toto TOTO, .titi TITI} :
|
|
61
|
+
{"toto": 5, "titi": 10, "x": 3} --> {"TOTO": 5, "TITI": 10}
|
|
62
|
+
{"titi": 10 } ... undefined
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
**QUIZ**: What is:
|
|
67
|
+
|
|
68
|
+
- empty composition: `()` ?
|
|
69
|
+
- empty merge: `<>` ?
|
|
70
|
+
- empty product: `{}` ?
|
|
71
|
+
- `{{{{} s} s} s}` ?
|
|
72
|
+
- `({{{() a} b} c} .c .b .a)` ?
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### Syntactic sugar
|
|
77
|
+
|
|
78
|
+
- parenthesis can be omitted, except for the empty composition `()`,
|
|
79
|
+
- dot (`.`) in "projection" acts as a separator so the space around it can be omitted.
|
|
80
|
+
|
|
81
|
+
For example, `[(.toto .titi (.0 .1))]` can be written as `[.toto.titi.0.1]`.
|
|
82
|
+
|
|
83
|
+
Comments can be introduced by `//`, `--`, `%`, or `#` and extends to
|
|
84
|
+
the end of line. Multiline `C`-like comments, `/* ... */`, are also
|
|
85
|
+
supported.
|
|
86
|
+
|
|
87
|
+
### Basic extensions
|
|
88
|
+
|
|
89
|
+
#### Constants, i.e., literals for `strings`, `integers`, `booleans`, and `null`
|
|
90
|
+
|
|
91
|
+
A constant defines a function which ignores its argument and produces
|
|
92
|
+
the constant value. E.g.:
|
|
93
|
+
|
|
94
|
+
{123 int, "kScript" str, true bool, null null} :
|
|
95
|
+
"any" --> {"int":123,"str":"kScript","bool":true,"null":null}
|
|
96
|
+
|
|
97
|
+
#### Vector product
|
|
98
|
+
|
|
99
|
+
Vector product can be seen as an abbreviation for product whose field
|
|
100
|
+
names are integers starting from zero. E.g., `{.toto 0, .titi 1, 123 2}`
|
|
101
|
+
can be written as `[.toto, .titi, 123]`.
|
|
102
|
+
|
|
103
|
+
[.toto, .titi, 12] :
|
|
104
|
+
{"toto": 5, "titi": 10 } --> [5, 10, 12]
|
|
105
|
+
|
|
106
|
+
.1 :
|
|
107
|
+
["A","B","C"] --> "B"
|
|
108
|
+
["a"] ... undefined
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### Pragmatic extensions, aka "standard library"
|
|
113
|
+
|
|
114
|
+
- `GT` : -- identity for lists of decreasing elements; undefined otherwise
|
|
115
|
+
|
|
116
|
+
[4,3] --> [4,3]
|
|
117
|
+
[3,4] ... undefined
|
|
118
|
+
[] --> []
|
|
119
|
+
[4,3,0] --> [4,3,0]
|
|
120
|
+
|
|
121
|
+
- `EQ` : -- identity for lists of equal elements; undefined otherwise
|
|
122
|
+
|
|
123
|
+
[4,4] --> [4,4]
|
|
124
|
+
[4,5] ... undefined
|
|
125
|
+
[4,4,4] --> [4,4,4]
|
|
126
|
+
[] --> []
|
|
127
|
+
|
|
128
|
+
- `{PLUS plus, TIMES times}` :
|
|
129
|
+
|
|
130
|
+
[1,2] --> {"plus":3,"times":2}
|
|
131
|
+
[2,2,2] --> {"plus":6,"times":8}
|
|
132
|
+
[] --> {"plus":0,"times":1}
|
|
133
|
+
|
|
134
|
+
- `CONCAT` :
|
|
135
|
+
|
|
136
|
+
["a","bc","d"] --> "abcd"
|
|
137
|
+
|
|
138
|
+
- `toJSON` :
|
|
139
|
+
|
|
140
|
+
{"a": 12} --> "{\"a\":12}"
|
|
141
|
+
|
|
142
|
+
- `fromJSON` :
|
|
143
|
+
|
|
144
|
+
"{\"a\":12}" --> {"a":12}
|
|
145
|
+
|
|
146
|
+
- other predefined parial functions are: `DIV`, `FDIV`, `CONS`, `SNOC`, `toDateMsec`,
|
|
147
|
+
`toDateStr`, and `_log!`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### Function definitions
|
|
152
|
+
|
|
153
|
+
dec = [(),-1] PLUS;
|
|
154
|
+
max = <SNOC [.0, .1 max] <GT.0, .1>, .0> ;
|
|
155
|
+
factorial = < [(),0] GT .0 [dec factorial, ()] TIMES, 1 >;
|
|
156
|
+
|
|
157
|
+
### Codes (_types_)
|
|
158
|
+
|
|
159
|
+
_Codes_ (prefixed by `$`) can be defined by taged union and product. E.g.:
|
|
160
|
+
|
|
161
|
+
$nat = <nat 1, {} 0>;
|
|
162
|
+
$pair = {nat x, nat y};
|
|
163
|
+
|
|
164
|
+
suc = {$nat 1};
|
|
165
|
+
add = $pair <{.x.1 x, .y suc y} add, .y>;
|
|
166
|
+
|
|
167
|
+
#### Basic extension codes
|
|
168
|
+
|
|
169
|
+
Since _basic extension_ introduces integers, booleans, and strings, there are three
|
|
170
|
+
predefined types: `int`, `bool`, and `string`. A vector product code can also be defined
|
|
171
|
+
by `[ codeExp ]`. All members of the vector are the same code. E.g.,
|
|
172
|
+
|
|
173
|
+
$intVector = [ int ];
|
|
174
|
+
$boolVector = [ bool ];
|
|
175
|
+
$tree = [ tree ];
|
|
176
|
+
|
|
177
|
+
emptyList? = $intVector $[ string ]; -- as only an empty vector can be a vector
|
|
178
|
+
-- of integers and a vector of strings
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Examples
|
|
182
|
+
|
|
183
|
+
1. projection:
|
|
37
184
|
|
|
38
185
|
.x
|
|
39
186
|
."field name"
|
|
@@ -42,7 +189,7 @@ other `k`-expressions in three ways:
|
|
|
42
189
|
The function is defined only if its argument is a structure with
|
|
43
190
|
the field (or a vector with the index).
|
|
44
191
|
|
|
45
|
-
|
|
192
|
+
2. _constants_, literals for Strings, Booleans, and
|
|
46
193
|
Integers. Examples:
|
|
47
194
|
|
|
48
195
|
"a string"
|
|
@@ -51,14 +198,12 @@ the field (or a vector with the index).
|
|
|
51
198
|
false
|
|
52
199
|
null
|
|
53
200
|
|
|
54
|
-
|
|
55
|
-
`TIMES`, `DIV`, `CONCAT`, `toJSON`, `fromJSON`, `CONS`,
|
|
56
|
-
`SNOC`, `toDateMsec`, `toDateStr`, and `_log!`. For example:
|
|
201
|
+
3. "built-in" functions:
|
|
57
202
|
|
|
58
|
-
[1, 2, 3] PLUS
|
|
59
|
-
[4, 4] TIMES toJSON
|
|
60
|
-
[3, 2] GT
|
|
61
|
-
[3, 4] GT ...
|
|
203
|
+
[1, 2, 3] PLUS -- integer constant function 6
|
|
204
|
+
[4, 4] TIMES toJSON -- string constant function "16"
|
|
205
|
+
[3, 2] GT -- vector constant function [3, 2]
|
|
206
|
+
[3, 4] GT -- ... undefined
|
|
62
207
|
|
|
63
208
|
A more interesting example could be:
|
|
64
209
|
|
|
@@ -68,7 +213,7 @@ the field (or a vector with the index).
|
|
|
68
213
|
|
|
69
214
|
[3,8] < GT .0, .1 > --> 8
|
|
70
215
|
|
|
71
|
-
|
|
216
|
+
### User defined functions
|
|
72
217
|
|
|
73
218
|
`k`-expression can be prefixed by function definitions. E.g.:
|
|
74
219
|
|
|
@@ -83,22 +228,22 @@ the field (or a vector with the index).
|
|
|
83
228
|
Another example could be finding the biggest (max) value in a vector:
|
|
84
229
|
|
|
85
230
|
max = <
|
|
86
|
-
SNOC
|
|
87
|
-
[.0, .1 max]
|
|
88
|
-
<GT .0, .1
|
|
89
|
-
|
|
90
|
-
.0
|
|
231
|
+
SNOC # [x0, x1, x2, ...] --> [x0, [x1, x2, ...]]
|
|
232
|
+
[.0, .1 max] # [x0, [x1, x2, ...]] --> [x0, max(x1,x2,...)], i.e., recursive call
|
|
233
|
+
<GT .0, .1> # if x0 > max(x1,x2,...) then x0 else max(x1,x2,...)
|
|
234
|
+
, # when SNOC is not defined, i.e., if the input vector has one element:
|
|
235
|
+
.0 # [x0] --> x0
|
|
91
236
|
>;
|
|
92
237
|
max
|
|
93
238
|
|
|
94
|
-
|
|
239
|
+
### Value encodings (_codes_)
|
|
95
240
|
|
|
96
241
|
There are three predefined value encodings: `int`, `string`, and
|
|
97
242
|
`bool`. The language supports `code`-expressions:
|
|
98
243
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
244
|
+
- product, e.g., `{int x, int y, bool flag}`
|
|
245
|
+
- disjoint union, e.g., `<{} true, {} false>`
|
|
246
|
+
- vector, e.g., `[ int ]` (all elements of the vector use the same
|
|
102
247
|
encoding)
|
|
103
248
|
|
|
104
249
|
One can define recursive codes. E.g.:
|
|
@@ -120,7 +265,9 @@ within `k`-expression is again prefixed by `$`.
|
|
|
120
265
|
> $ int;
|
|
121
266
|
height
|
|
122
267
|
|
|
123
|
-
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## `k` (a command-line JSON processor using `k` syntax)
|
|
124
271
|
|
|
125
272
|
There is a wrapper, `./node_modules/.bin/k` , which makes it easy to
|
|
126
273
|
run the language from command line.
|
|
@@ -130,7 +277,7 @@ run the language from command line.
|
|
|
130
277
|
Usage: ./node_modules/.bin/k ( k-expr | -k k-file) [ -1 ] [ json-file ]
|
|
131
278
|
E.g., cat '{"a": 10}' | ./node_modules/.bin/k '[(),()]'
|
|
132
279
|
|
|
133
|
-
|
|
280
|
+
For example:
|
|
134
281
|
|
|
135
282
|
1. One `k`-expression with one `json`-object:
|
|
136
283
|
|
|
@@ -148,6 +295,8 @@ run the language from command line.
|
|
|
148
295
|
{"z": 123} --> {"x&y":"no x nor y"}
|
|
149
296
|
^D - to interrupt
|
|
150
297
|
|
|
298
|
+
If the input is a multiline json object, we need to add `-1` to the command-line options.
|
|
299
|
+
|
|
151
300
|
3. If the `k`-expression is long, it can be put in a file, e.g.:
|
|
152
301
|
|
|
153
302
|
> cat test.k
|
|
@@ -186,7 +335,36 @@ run the language from command line.
|
|
|
186
335
|
{"z": 123}
|
|
187
336
|
####################################################
|
|
188
337
|
|
|
189
|
-
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
### Short comparaison with `jq` tutorial examples: <https://stedolan.github.io/jq/tutorial/>
|
|
341
|
+
|
|
342
|
+
1.
|
|
343
|
+
curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' | jq '.'
|
|
344
|
+
curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' | k '()' -1
|
|
345
|
+
2.
|
|
346
|
+
curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' | jq '.[0]'
|
|
347
|
+
curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' | k '.0' -1
|
|
348
|
+
3.
|
|
349
|
+
jq '.[0] | {message: .commit.message, name: .commit.committer.name}'
|
|
350
|
+
k '.0 {.commit.message message, .commit.committer.name name}' -1
|
|
351
|
+
4. note: `k`-expression defines a partial function yielding a single json object, i.e., as far as `k` is concerned,
|
|
352
|
+
examples 4 and 5 of the `jq` tutorial are equivalent.
|
|
353
|
+
5.
|
|
354
|
+
jq '[.[] | {message: .commit.message, name: .commit.committer.name}]'
|
|
355
|
+
k 'f = .commit {.message message, .committer.name name}; %\
|
|
356
|
+
map_f = <SNOC [.0 f,.1 map_f] CONS, [.0 f], []>; %\
|
|
357
|
+
map_f' -1
|
|
358
|
+
6.
|
|
359
|
+
jq '[.[] | {message: .commit.message, name: .commit.committer.name, parents: [.parents[].html_url]}]'
|
|
360
|
+
k 'map_html_url = <SNOC [.0 .html_url, .1 map_html_url] CONS, [.0 .html_url], []>; %\
|
|
361
|
+
f = {.commit.message message, .commit.committer.name name, .parents map_html_url parents}; %\
|
|
362
|
+
map_f = <SNOC [.0 f, .1 map_f] CONS, [.0 f] , []>; %\
|
|
363
|
+
map_f' -1
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## k-REPL (Read-Evaluate-Print Loop)
|
|
190
368
|
|
|
191
369
|
Also there is a REPL, `./node_modules/.bin/k-repl`, which acts like a toy
|
|
192
370
|
shell for the language. E.g.:
|
|
@@ -201,11 +379,13 @@ shell for the language. E.g.:
|
|
|
201
379
|
inc inc inc inc
|
|
202
380
|
=> 7
|
|
203
381
|
|
|
204
|
-
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Using `k` from `javascript`
|
|
205
385
|
|
|
206
|
-
|
|
386
|
+
import k from "@fraczak/k";
|
|
207
387
|
|
|
208
|
-
k_expression = '()';
|
|
388
|
+
let k_expression = '()';
|
|
209
389
|
k.run(k_expression,"ANYTHING...");
|
|
210
390
|
// RETURNS: "ANYTHING..."
|
|
211
391
|
|
|
@@ -248,7 +428,7 @@ shell for the language. E.g.:
|
|
|
248
428
|
k_fn({"age":2,"name":"Mark"});
|
|
249
429
|
// RETURNS: {"nom":"Mark","age":"enfant"}
|
|
250
430
|
|
|
251
|
-
|
|
431
|
+
k_fn = k.compile('$t = < i: int, t: [ t ] > ; <$t, $int>');
|
|
252
432
|
|
|
253
433
|
k_fn(1);
|
|
254
434
|
// RETURNS: 1
|
|
@@ -262,7 +442,7 @@ shell for the language. E.g.:
|
|
|
262
442
|
k_fn({"t":[{"i":2},{"i":3},{"t":[]}]});
|
|
263
443
|
// RETURNS: {"t":[{"i":2},{"i":3},{"t":[]}]}
|
|
264
444
|
|
|
265
|
-
|
|
445
|
+
k_fn = k.compile('$ < < [ int ] ints, [ bool ] bools > list, string None>');
|
|
266
446
|
|
|
267
447
|
k_fn({"None":"None"});
|
|
268
448
|
// RETURNS: {"None":"None"}
|
package/codes.mjs
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
function are_different(classes, representatives, name1, name2, codes) {
|
|
2
|
+
if (name1 === name2) return false;
|
|
3
|
+
const code1 = codes[name1];
|
|
4
|
+
const code2 = codes[name2];
|
|
5
|
+
if (code1.code !== code2.code) return true;
|
|
6
|
+
switch (code1.code) {
|
|
7
|
+
case "union":
|
|
8
|
+
case "product":
|
|
9
|
+
const [fields1, fields2] = [code1, code2].map((code) => {
|
|
10
|
+
const arg = code[code.code];
|
|
11
|
+
return Object.keys(arg).reduce((fields, label) => {
|
|
12
|
+
const ref = representatives[arg[label]];
|
|
13
|
+
fields[label] = ref != null ? ref : arg[label];
|
|
14
|
+
return fields;
|
|
15
|
+
}, {});
|
|
16
|
+
});
|
|
17
|
+
if (Object.keys(fields1).length !== Object.keys(fields2).length)
|
|
18
|
+
return true;
|
|
19
|
+
|
|
20
|
+
for (const field in fields1) {
|
|
21
|
+
if (fields2[field] !== fields1[field]) return true;
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
case "vector":
|
|
25
|
+
const [arg1, arg2] = [code1, code2].map(
|
|
26
|
+
({ vector }) => representatives[vector] || vector
|
|
27
|
+
);
|
|
28
|
+
if (arg1 !== arg2) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function minimize(codes) {
|
|
34
|
+
const names = Object.keys(codes);
|
|
35
|
+
const classes = {};
|
|
36
|
+
classes["{}"] = names;
|
|
37
|
+
const representatives = names.reduce((representatives, name) => {
|
|
38
|
+
representatives[name] = "{}";
|
|
39
|
+
return representatives;
|
|
40
|
+
}, {});
|
|
41
|
+
let changed = true;
|
|
42
|
+
while (changed) {
|
|
43
|
+
changed = false;
|
|
44
|
+
for (const name1 in classes) {
|
|
45
|
+
const [eq_names, dif_names] = classes[name1].reduce(
|
|
46
|
+
([eq_names, dif_names], name2) => {
|
|
47
|
+
if (are_different(classes, representatives, name1, name2, codes)) {
|
|
48
|
+
dif_names.push(name2);
|
|
49
|
+
} else {
|
|
50
|
+
eq_names.push(name2);
|
|
51
|
+
}
|
|
52
|
+
return [eq_names, dif_names];
|
|
53
|
+
},
|
|
54
|
+
[[], []]
|
|
55
|
+
);
|
|
56
|
+
classes[name1] = eq_names;
|
|
57
|
+
|
|
58
|
+
if (dif_names.length > 0) {
|
|
59
|
+
const new_rep = dif_names[0];
|
|
60
|
+
changed = true;
|
|
61
|
+
classes[new_rep] = dif_names;
|
|
62
|
+
dif_names.forEach((name) => (representatives[name] = new_rep));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { classes, representatives };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalize(label_ref_map, representatives) {
|
|
70
|
+
return Object.keys(label_ref_map).reduce((result, label) => {
|
|
71
|
+
const name = label_ref_map[label];
|
|
72
|
+
result[label] = representatives[name] || name;
|
|
73
|
+
return result;
|
|
74
|
+
}, {});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeAll(codes, representatives) {
|
|
78
|
+
return Object.keys(codes).reduce(function (normalized, name) {
|
|
79
|
+
if (name === representatives[name]) {
|
|
80
|
+
const code = codes[name];
|
|
81
|
+
switch (code.code) {
|
|
82
|
+
case "union":
|
|
83
|
+
case "product":
|
|
84
|
+
normalized[name] = { code: code.code };
|
|
85
|
+
normalized[name][code.code] = normalize(
|
|
86
|
+
code[code.code],
|
|
87
|
+
representatives
|
|
88
|
+
);
|
|
89
|
+
break;
|
|
90
|
+
case "vector":
|
|
91
|
+
normalized[name] = {
|
|
92
|
+
code: "vector",
|
|
93
|
+
vector: representatives[code.vector] || code.vector,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return normalized;
|
|
99
|
+
}, {});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default { minimize, normalize, normalizeAll };
|
|
103
|
+
export { minimize, normalize, normalizeAll };
|
package/index.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parse } from "./parser.mjs";
|
|
2
|
+
import run from "./run.mjs";
|
|
3
|
+
import t from "./codes.mjs";
|
|
4
|
+
|
|
5
|
+
function finalize(codes) {
|
|
6
|
+
const representatives = t.minimize(codes).representatives;
|
|
7
|
+
return {
|
|
8
|
+
codes: t.normalizeAll(codes, representatives),
|
|
9
|
+
representatives,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function compile(script) {
|
|
14
|
+
const { defs, exp } = parse(script);
|
|
15
|
+
const { codes, representatives } = finalize(defs.codes);
|
|
16
|
+
run.defs = {
|
|
17
|
+
codes,
|
|
18
|
+
representatives,
|
|
19
|
+
rels: defs.rels,
|
|
20
|
+
};
|
|
21
|
+
return run.bind(null, exp);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
compile.doc = "Transforms k-script (string) into a function";
|
|
25
|
+
|
|
26
|
+
function runScriptOnData(script, data) {
|
|
27
|
+
return compile(script)(data);
|
|
28
|
+
}
|
|
29
|
+
runScriptOnData.doc = "Run 'script' (string) on 'data': (script,data) -> data";
|
|
30
|
+
|
|
31
|
+
export default { compile, run: runScriptOnData, parse };
|
|
32
|
+
export { compile, runScriptOnData as run, parse };
|
package/k.mjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import k from "./index.mjs";
|
|
5
|
+
|
|
6
|
+
const splice = [].splice;
|
|
7
|
+
|
|
8
|
+
const prog = process.argv[1];
|
|
9
|
+
|
|
10
|
+
let kScript, jsonStream, oneJson;
|
|
11
|
+
|
|
12
|
+
({ kScript, jsonStream, oneJson } = ((oneJson, args) => {
|
|
13
|
+
let e;
|
|
14
|
+
try {
|
|
15
|
+
kScript = (function (arg) {
|
|
16
|
+
if (arg == null) {
|
|
17
|
+
throw new Error();
|
|
18
|
+
}
|
|
19
|
+
if (arg === "-k") {
|
|
20
|
+
return fs.readFileSync(args.shift()).toString("utf8");
|
|
21
|
+
} else {
|
|
22
|
+
return arg;
|
|
23
|
+
}
|
|
24
|
+
})(args.shift());
|
|
25
|
+
kScript = k.compile(kScript);
|
|
26
|
+
jsonStream = (function (arg) {
|
|
27
|
+
if (arg === "-1") {
|
|
28
|
+
oneJson = true;
|
|
29
|
+
arg = args.shift();
|
|
30
|
+
}
|
|
31
|
+
if (arg == null) {
|
|
32
|
+
return process.stdin;
|
|
33
|
+
}
|
|
34
|
+
return fs.createReadStream(arg);
|
|
35
|
+
})(args.shift());
|
|
36
|
+
return { kScript, jsonStream, oneJson };
|
|
37
|
+
} catch (error) {
|
|
38
|
+
e = error;
|
|
39
|
+
console.error(e);
|
|
40
|
+
console.error(`Usage: ${prog} ( k-expr | -k k-file ) [ -1 ] [ json-file ]`);
|
|
41
|
+
console.error(` E.g., echo '{\"a\": 12}' | ${prog} '[(),()]'`);
|
|
42
|
+
return process.exit(-1);
|
|
43
|
+
}
|
|
44
|
+
})(false, process.argv.slice(2)));
|
|
45
|
+
|
|
46
|
+
if (oneJson) {
|
|
47
|
+
const buffer = [];
|
|
48
|
+
jsonStream.on("data", (data) => buffer.push(data));
|
|
49
|
+
jsonStream.on("end", () => {
|
|
50
|
+
try {
|
|
51
|
+
console.log(JSON.stringify(kScript(JSON.parse(buffer.join(""))), null, 2));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
let buffer = [];
|
|
58
|
+
let line = 0;
|
|
59
|
+
jsonStream.on("data", (data) => {
|
|
60
|
+
const [first, ...rest] = data.toString("utf8").split("\n");
|
|
61
|
+
buffer.push(first);
|
|
62
|
+
if (rest.length > 0) {
|
|
63
|
+
const todo = buffer.join("");
|
|
64
|
+
const last = rest.pop();
|
|
65
|
+
buffer = [last];;
|
|
66
|
+
for (const exp of [todo, ...rest]) {
|
|
67
|
+
if (!exp.match(/^[ \n\t]*(?:#.*)?$/)) {
|
|
68
|
+
try {
|
|
69
|
+
console.log(JSON.stringify(kScript(JSON.parse(exp))));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(`Problem [line ${line}]: '${exp}'`);
|
|
72
|
+
console.error(error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
line++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "k-language for JSON-like data transformation",
|
|
5
|
-
"main": "index.
|
|
5
|
+
"main": "index.mjs",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"prepare": "
|
|
8
|
-
"
|
|
9
|
-
"all": "coffee -c *.coffee; npm run prepare; npm run prepublishOnly",
|
|
10
|
-
"test": "coffee -c symbol-table.coffee; jiwson parser.jison; coffee test.coffee"
|
|
7
|
+
"prepare": "jiwson -m es6 parser.jison -o parser.mjs",
|
|
8
|
+
"test": "node test.mjs"
|
|
11
9
|
},
|
|
12
10
|
"repository": {
|
|
13
11
|
"type": "git",
|
|
@@ -16,11 +14,10 @@
|
|
|
16
14
|
"author": "Wojciech Fraczak",
|
|
17
15
|
"license": "ISC",
|
|
18
16
|
"bin": {
|
|
19
|
-
"k": "./k.
|
|
20
|
-
"k-repl": "./repl.
|
|
17
|
+
"k": "./k.mjs",
|
|
18
|
+
"k-repl": "./repl.mjs"
|
|
21
19
|
},
|
|
22
20
|
"devDependencies": {
|
|
23
|
-
"coffeescript": "^2.7.0",
|
|
24
21
|
"jiwson": "^0.1.1"
|
|
25
22
|
}
|
|
26
23
|
}
|
package/parser.jison
CHANGED
package/{parser.js → parser.mjs}
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
/* parser generated by jison 0.1.1 */
|
|
2
3
|
/*
|
|
3
4
|
Returns a Parser object of the following structure:
|
|
@@ -74,7 +75,7 @@
|
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
import s from "./symbol-table.mjs";
|
|
78
79
|
|
|
79
80
|
function fromEscString(escString) {
|
|
80
81
|
const isSingleQuoted = escString.startsWith("'");
|
|
@@ -756,20 +757,11 @@ Parser.prototype = parser;parser.Parser = Parser;
|
|
|
756
757
|
return new Parser;
|
|
757
758
|
})();
|
|
758
759
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
console.log('Usage: '+args[0]+' FILE');
|
|
767
|
-
process.exit(1);
|
|
768
|
-
}
|
|
769
|
-
var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
|
|
770
|
-
return exports.parser.parse(source);
|
|
771
|
-
};
|
|
772
|
-
if (typeof module !== 'undefined' && require.main === module) {
|
|
773
|
-
exports.main(process.argv.slice(1));
|
|
760
|
+
const ___parser = parser;
|
|
761
|
+
const ___Parser = parser.Parser;
|
|
762
|
+
const ___parse = parser.parse.bind(parser);
|
|
763
|
+
export {
|
|
764
|
+
___parser as parser,
|
|
765
|
+
___Parser as Parser,
|
|
766
|
+
___parse as parse
|
|
774
767
|
}
|
|
775
|
-
}
|