@fraczak/k 1.0.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/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # k-language
2
+
3
+ Another JSON-to-JSON transformation notation. A `k`-expression
4
+ (script) defines a __partial function__, i.e., a function which, in
5
+ general, is not defined for every input. `k`-expressions can be
6
+ combined to build other `k`-expressions in three ways:
7
+
8
+ 1. composition, e.g.:
9
+
10
+ (E1 E2 E3 ...)
11
+
12
+ Apart from empty composition, `()`, which defines _identity_, the paranthesis can be omitted.
13
+
14
+ 2. product, e.g., vector:
15
+
16
+ [ E1, E2, E3, ... ]
17
+
18
+ or structure:
19
+
20
+ { E1 "e1", E2 "e2", E3 "e3", ... }
21
+
22
+ 3. union (merge), e.g.:
23
+
24
+ < E1, E2, E3, ... >
25
+
26
+ Elementary partial functions are:
27
+
28
+ 1. _projection_, i.e., extracting the value of a given field or
29
+ index. Examples:
30
+
31
+ .x
32
+ ."field name"
33
+ .4
34
+
35
+ The function is defined only if its argument is a structure with
36
+ the field (or a vector with the index).
37
+
38
+ 2. _constants_, literals for Strings, Booleans, and
39
+ Integers. Examples:
40
+
41
+ "a string"
42
+ 'another "String"'
43
+ 123
44
+ false
45
+
46
+ Those functions are always defined, i.e., they ignore their argument.
47
+
48
+ 3. There is a number of built-in functions: `GT`, `EQ`, `PLUS`,
49
+ `TIMES`, `MINUS`, `DIV`, `CONCAT`, `toJSON`, `fromJSON`. For example
50
+
51
+ [1, 2, 3] PLUS --> integer constant 6
52
+ [4, 4] TIMES toJSON --> string constant "16"
53
+ [3, 2] GT --> vector constant [3, 2]
54
+ [3, 4] GT --> is not defined!
55
+
56
+ A more interesting example could be:
57
+
58
+ < [.0, .1] GT, [.1, .0] GT > .0
59
+
60
+ which selects the bigger of two first integers of a vector argument, i.e.,
61
+
62
+ [3,8] < [.0, .1] GT, [.1, .0] GT > .0 --> 8
63
+
64
+
65
+
66
+ ## Command line script
67
+
68
+ There is a wrapper, `./node_modules/.bin/k` , which makes it easy to
69
+ run the language from command line.
70
+
71
+ $ ./node_modules/.bin/k
72
+ ... errors ...
73
+ Usage: ./node_modules/.bin/k ( k-expr | -k k-file) [ json-file ]
74
+ E.g., cat '{"a": 10}' | ./node_modules/.bin/k '[(),()]'
75
+
76
+ ### Examples
77
+
78
+ 1. One `k`-expression with one `json`-object:
79
+
80
+ $ echo '{"x": 12, "y": 13}' | ./k.coffee '{ <.x, "no x"> x, () input}'
81
+ {"x":12,"input":{"x":12,"y":13}}
82
+
83
+ 2. By providing only `k`-expression, the script will compile the
84
+ `k`-expression and apply the generated function to the `stdin`, line by line:
85
+
86
+ $ ./k.coffee '<["x=",.x," & y=",.y],["only x=",.x],["only y=",.y],["no x nor y"]>{CONCAT "x&y"}'
87
+
88
+ {"y": 123, "x": 432,"others": "..."} --> {"x&y":"x=432 & y=123"}
89
+ {"x":987} --> {"x&y":"only x=987"}
90
+ {"z":123} --> {"x&y":"no x nor y"}
91
+ ^D - to interrupt
92
+
93
+ 3. If the `k`-expression is long, it can be put in a file, e.g.:
94
+
95
+ $ cat test.k
96
+ --------- comments start by #, --, or // ----------------------------------
97
+ < -- merge of 4 partial functions...
98
+ ["x=", .x, " & y=", .y], -- produces a vector of 4 values, if fields 'x' and 'y' are present
99
+ ["only x=", .x], -- produces a pair '["only x=", "value-of-x"]', for input like {"x":"value-of-x"}
100
+ -- it is defined only if field 'x' is present
101
+ ["only y=", .y],
102
+ ["no x nor y"] -- defined for all input, returns always the same one element vector
103
+ >
104
+ -- one of the string vectors is passed to the following partial function,
105
+ -- which produces a record (map) with one field "x&y", whose value is the
106
+ -- result of concatenating elements of the passed in vector
107
+ { CONCAT "x&y" }
108
+ ------------------------------------------------------------------------------
109
+
110
+ We can use it by:
111
+
112
+ $ ./k.coffee -k test.k
113
+
114
+ If we want to read `json` objects from a file, e.g., `my-objects.json`, we do
115
+
116
+ $ ./k.coffee -k test.k -j my-objects.json
117
+ {"x&y":"x=432 & y=123"}
118
+ {"x&y":"onlyx=987"}
119
+ {"x&y":"no x nor y"}
120
+
121
+ where:
122
+
123
+ $ cat my-objects.json
124
+ ####################################################
125
+ # empty lines and lines starting with # are ignored
126
+ {"y": 123, "x": 432,"others": "..."}
127
+ {"x":987}
128
+ {"z":123}
129
+ ####################################################
130
+
131
+ # Using from `javascript`
132
+
133
+ var k = require("k");
134
+
135
+ var k_expression;
136
+
137
+ k_expression = '()';
138
+ k.run(t_expression,"ANYTHING...");
139
+ // RETURNS: "ANYTHING..."
140
+
141
+ k_expression = '{"ala" name, 23 age}';
142
+ k.run(k_expression,"ANYTHING...");
143
+ // RETURNS: {"name":"ala","age":23}
144
+
145
+ k_expression = '[.year, .age]';
146
+ k.run(k_expression,{"year":2002,"age":19});
147
+ // RETURNS: [2002,19]
148
+
149
+ k_expression = '[(), ()]';
150
+ k.run(k_expression,"duplicate me");
151
+ // RETURNS: ["duplicate me","duplicate me"]
152
+
153
+ k_expression = '[[[()]]]';
154
+ k.run(k_expression,"nesting");
155
+ // RETURNS: [[["nesting"]]]
156
+
157
+ k_expression = '[[()]] {() nested, .0.0 val}';
158
+ k.run(k_expression,"nesting and accessing")
159
+ // RETURNS: {"nested":[["nesting and accessing"]],"val":"nesting and accessing"}
160
+
161
+ k_expression = '0000';
162
+ k.run(k_expression,{"test":"parse integer"});
163
+ // RETURNS: 0
164
+
165
+ k_expression = '[.y,.x] PLUS';
166
+ k.run(k_expression,{"x":3,"y":4});
167
+ // RETURNS: 7
168
+
169
+ k_expression = '{.name nom, <[.age, 18] GT .0, [.age, 12] GT "ado", "enfant"> age}';
170
+
171
+ var k_fn = k.compile(k_expression);
172
+
173
+ k_fn({"age":23,"name":"Emily"});
174
+ // RETURNS: {"nom":"Emily","age":23}
175
+
176
+ k_fn({"age":16,"name":"Katrina"});
177
+ // RETURNS: {"nom":"Katrina","age":"ado"}
178
+
179
+ k_fn({"age":2,"name":"Mark"})
180
+ // RETURNS: {"nom":"Mark","age":"enfant"}
181
+
package/index.coffee ADDED
@@ -0,0 +1,18 @@
1
+ fs = require "fs"
2
+ {parse} = require "./parser.js"
3
+ run = require "./run"
4
+
5
+ compile = (script) ->
6
+ do (exp = parse script) ->
7
+ (data) -> run exp, data
8
+ compile.doc = "Transforms k-script (string) into a function"
9
+
10
+ compile_file = (fileName) ->
11
+ compile fs.readFileSync(fileName).toString "utf8"
12
+ compile_file.doc = "Transforms k-script (from file) into a function"
13
+
14
+ runScriptOnData = (script, data) ->
15
+ compile(script) data
16
+ runScriptOnData.doc = "Run 'script' (string) on 'data': (script,data) -> data"
17
+
18
+ module.exports = {compile, compile_file, run: runScriptOnData, parse: parse}
package/index.js ADDED
@@ -0,0 +1,40 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ (function() {
3
+ var compile, compile_file, fs, parse, run, runScriptOnData;
4
+
5
+ fs = require("fs");
6
+
7
+ ({parse} = require("./parser.js"));
8
+
9
+ run = require("./run");
10
+
11
+ compile = function(script) {
12
+ return (function(exp) {
13
+ return function(data) {
14
+ return run(exp, data);
15
+ };
16
+ })(parse(script));
17
+ };
18
+
19
+ compile.doc = "Transforms k-script (string) into a function";
20
+
21
+ compile_file = function(fileName) {
22
+ return compile(fs.readFileSync(fileName).toString("utf8"));
23
+ };
24
+
25
+ compile_file.doc = "Transforms k-script (from file) into a function";
26
+
27
+ runScriptOnData = function(script, data) {
28
+ return compile(script)(data);
29
+ };
30
+
31
+ runScriptOnData.doc = "Run 'script' (string) on 'data': (script,data) -> data";
32
+
33
+ module.exports = {
34
+ compile,
35
+ compile_file,
36
+ run: runScriptOnData,
37
+ parse: parse
38
+ };
39
+
40
+ }).call(this);
package/k.coffee ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env coffee
2
+ fs = require "fs"
3
+ k = require "./"
4
+
5
+ prog = process.argv[1]
6
+
7
+ {kScript,jsonStream} = do (args = process.argv.slice(2)) ->
8
+ try
9
+ kScript = do (arg = args.shift()) ->
10
+ throw new Error() unless arg?
11
+ if arg is "-k"
12
+ fs.readFileSync(args.shift()).toString "utf8"
13
+ else
14
+ arg
15
+
16
+ kScript = k.compile kScript
17
+
18
+ jsonStream = do (arg = args.shift()) ->
19
+ return process.stdin unless arg?
20
+ fs.createReadStream args.shift()
21
+
22
+ {kScript, jsonStream}
23
+ catch e
24
+ console.error e
25
+ console.error "Usage: #{prog} ( k-expr | -k k-file ) [ json-file ]"
26
+ console.error " E.g., echo '{\"a\": 12}' | #{prog} '[(),()]'"
27
+ process.exit -1
28
+
29
+ do (buffer = [], line = 0) ->
30
+ jsonStream.on 'data', (data) ->
31
+ [first, rest...] = data.toString("utf8").split "\n"
32
+ buffer.push first
33
+ if rest.length > 0
34
+ todo = buffer.join ""
35
+ [rest..., last] = rest
36
+ buffer = [last]
37
+ todo = [todo, rest...]
38
+ for json in todo
39
+ if not json.match /^[ \n\t]*(?:#.*)?$/
40
+ try
41
+ console.log JSON.stringify kScript JSON.parse json
42
+ catch e
43
+ console.error "Problem [line #{line}]: '#{json}'"
44
+ console.error e
45
+ line = line + 1
package/k.js ADDED
@@ -0,0 +1,72 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ (function() {
3
+ //!/usr/bin/env coffee
4
+ var fs, jsonStream, k, kScript, prog,
5
+ splice = [].splice;
6
+
7
+ fs = require("fs");
8
+
9
+ k = require("./");
10
+
11
+ prog = process.argv[1];
12
+
13
+ ({kScript, jsonStream} = (function(args) {
14
+ var e;
15
+ try {
16
+ kScript = (function(arg) {
17
+ if (arg == null) {
18
+ throw new Error();
19
+ }
20
+ if (arg === "-k") {
21
+ return fs.readFileSync(args.shift()).toString("utf8");
22
+ } else {
23
+ return arg;
24
+ }
25
+ })(args.shift());
26
+ kScript = k.compile(kScript);
27
+ jsonStream = (function(arg) {
28
+ if (arg == null) {
29
+ return process.stdin;
30
+ }
31
+ return fs.createReadStream(args.shift());
32
+ })(args.shift());
33
+ return {kScript, jsonStream};
34
+ } catch (error) {
35
+ e = error;
36
+ console.error(e);
37
+ console.error(`Usage: ${prog} ( k-expr | -k k-file ) [ json-file ]`);
38
+ console.error(` E.g., echo '{\"a\": 12}' | ${prog} '[(),()]'`);
39
+ return process.exit(-1);
40
+ }
41
+ })(process.argv.slice(2)));
42
+
43
+ (function(buffer, line) {
44
+ return jsonStream.on('data', function(data) {
45
+ var e, first, i, json, last, len, ref, rest, results, todo;
46
+ [first, ...rest] = data.toString("utf8").split("\n");
47
+ buffer.push(first);
48
+ if (rest.length > 0) {
49
+ todo = buffer.join("");
50
+ ref = rest, [...rest] = ref, [last] = splice.call(rest, -1);
51
+ buffer = [last];
52
+ todo = [todo, ...rest];
53
+ results = [];
54
+ for (i = 0, len = todo.length; i < len; i++) {
55
+ json = todo[i];
56
+ if (!json.match(/^[ \n\t]*(?:#.*)?$/)) {
57
+ try {
58
+ console.log(JSON.stringify(kScript(JSON.parse(json))));
59
+ } catch (error) {
60
+ e = error;
61
+ console.error(`Problem [line ${line}]: '${json}'`);
62
+ console.error(e);
63
+ }
64
+ }
65
+ results.push(line = line + 1);
66
+ }
67
+ return results;
68
+ }
69
+ });
70
+ })([], 0);
71
+
72
+ }).call(this);
package/k.sh ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // Generated by CoffeeScript 2.7.0
3
+ (function() {
4
+ //!/usr/bin/env coffee
5
+ var fs, jsonStream, k, kScript, prog,
6
+ splice = [].splice;
7
+
8
+ fs = require("fs");
9
+
10
+ k = require("./");
11
+
12
+ prog = process.argv[1];
13
+
14
+ ({kScript, jsonStream} = (function(args) {
15
+ var e;
16
+ try {
17
+ kScript = (function(arg) {
18
+ if (arg == null) {
19
+ throw new Error();
20
+ }
21
+ if (arg === "-k") {
22
+ return fs.readFileSync(args.shift()).toString("utf8");
23
+ } else {
24
+ return arg;
25
+ }
26
+ })(args.shift());
27
+ kScript = k.compile(kScript);
28
+ jsonStream = (function(arg) {
29
+ if (arg == null) {
30
+ return process.stdin;
31
+ }
32
+ return fs.createReadStream(args.shift());
33
+ })(args.shift());
34
+ return {kScript, jsonStream};
35
+ } catch (error) {
36
+ e = error;
37
+ console.error(e);
38
+ console.error(`Usage: ${prog} ( k-expr | -k k-file ) [ json-file ]`);
39
+ console.error(` E.g., echo '{\"a\": 12}' | ${prog} '[(),()]'`);
40
+ return process.exit(-1);
41
+ }
42
+ })(process.argv.slice(2)));
43
+
44
+ (function(buffer, line) {
45
+ return jsonStream.on('data', function(data) {
46
+ var e, first, i, json, last, len, ref, rest, results, todo;
47
+ [first, ...rest] = data.toString("utf8").split("\n");
48
+ buffer.push(first);
49
+ if (rest.length > 0) {
50
+ todo = buffer.join("");
51
+ ref = rest, [...rest] = ref, [last] = splice.call(rest, -1);
52
+ buffer = [last];
53
+ todo = [todo, ...rest];
54
+ results = [];
55
+ for (i = 0, len = todo.length; i < len; i++) {
56
+ json = todo[i];
57
+ if (!json.match(/^[ \n\t]*(?:#.*)?$/)) {
58
+ try {
59
+ console.log(JSON.stringify(kScript(JSON.parse(json))));
60
+ } catch (error) {
61
+ e = error;
62
+ console.error(`Problem [line ${line}]: '${json}'`);
63
+ console.error(e);
64
+ }
65
+ }
66
+ results.push(line = line + 1);
67
+ }
68
+ return results;
69
+ }
70
+ });
71
+ })([], 0);
72
+
73
+ }).call(this);
@@ -0,0 +1,6 @@
1
+ ####################################################
2
+ # empty lines and lines starting with # are ignored
3
+ {"y": 123, "x": 432,"others": "..."}
4
+ {"x":987}
5
+ {"z":123}
6
+ ####################################################
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@fraczak/k",
3
+ "version": "1.0.0",
4
+ "description": "k-language for untyped JSON like data transformation",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "prepare": "coffee -c symbol-table.coffee; jison parser.jison",
8
+ "prepublishOnly": "coffee -c *.coffee; echo '#!/usr/bin/env node' | cat - k.js > k.sh",
9
+ "test": "coffee -c symbol-table.coffee; jison parser.jison; coffee test.coffee;"
10
+ },
11
+ "author": "Wojciech Fraczak",
12
+ "license": "ISC",
13
+ "bin": {
14
+ "k": "./k.sh"
15
+ },
16
+ "devDependencies": {
17
+ "jison": "^0.4.18"
18
+ },
19
+ "dependencies": {
20
+ "coffeescript": "^2.7.0"
21
+ }
22
+ }
package/parser.jison ADDED
@@ -0,0 +1,93 @@
1
+ %{
2
+
3
+ var s = require("./symbol-table");
4
+
5
+ %}
6
+
7
+ %lex
8
+
9
+ %%
10
+ [/][*]([*][^/]|[^*])*[*][/] /* c-comment */
11
+ ("//"|"#"|"%"|"--")[^\n]* /* one line comment */
12
+ \s+ /* blanks */
13
+ "<" return 'LA';
14
+ "{" return 'LC';
15
+ "[" return 'LB';
16
+ "(" return 'LP';
17
+ ">" return 'RA';
18
+ "}" return 'RC';
19
+ "]" return 'RB';
20
+ ")" return 'RP';
21
+ "." return 'DOT';
22
+ "," return 'COMMA';
23
+ \"[^\"\n]*\"|\'[^\'\n]*\' return 'STRING'
24
+ [a-zA-Z_][a-zA-Z0-9_]* return 'NAME';
25
+ 0|[-]?[1-9][0-9]* return 'INT';
26
+ <<EOF>> return 'EOF';
27
+
28
+ /lex
29
+
30
+ %token NAME STRING INT
31
+ %token LA LC LB LP RA RP RB RC COMMA
32
+ %token EOF
33
+
34
+ %start input_with_eof
35
+
36
+ %%
37
+
38
+ name: NAME { $$ = String(yytext); };
39
+ str: STRING { $$ = String(yytext).slice(1,-1); };
40
+ int: INT { $$ = parseInt(String(yytext)); };
41
+
42
+ input_with_eof: comp EOF {
43
+ // console.log(JSON.stringify($1, "", 2));
44
+ return $1;
45
+ };
46
+
47
+ comp
48
+ : exp { $$ = $1; }
49
+ | comp exp { $$ = s.comp($1, $2); }
50
+ ;
51
+
52
+ exp
53
+ : LC labelled RC { $$ = $2; }
54
+ | LB list RB { $$ = {op: "vector", vector: $2}; }
55
+ | LA list RA { $$ = {op: "union", union: $2}; }
56
+ | name { $$ = {op: "ref", ref: $1}; }
57
+ | LP RP { $$ = s.identity; }
58
+ | LP comp RP { $$ = $2; }
59
+ | str { $$ = {op: "str", str: $1}; }
60
+ | int { $$ = {op: "int", int: $1}; }
61
+ | DOT int { $$ = {op: "dot", dot: $2}; }
62
+ | DOT str { $$ = {op: "dot", dot: $2}; }
63
+ | DOT name { $$ = {op: "dot", dot: $2}; }
64
+ ;
65
+
66
+ labelled
67
+ : { $$ = {op: "product", product: []}; }
68
+ | non_empty_labelled { $$ = $1; }
69
+ ;
70
+
71
+ non_empty_labelled
72
+ : comp_label
73
+ { $$ = {op: "product", product: [$1]}; }
74
+ | non_empty_labelled COMMA comp_label
75
+ { $1.product = [].concat($1.product,$3); $$ = $1; }
76
+ ;
77
+
78
+ comp_label
79
+ : comp name { $$ = {label: $2, exp: $1}; }
80
+ | comp str { $$ = {label: $2, exp: $1}; }
81
+ ;
82
+
83
+ list
84
+ : /*empty */ { $$ = []; }
85
+ | non_empty_list { $$ = $1; }
86
+ ;
87
+
88
+ non_empty_list
89
+ : comp { $$ = [$1]; }
90
+ | non_empty_list COMMA comp { $$ = [].concat($1,$3); }
91
+ ;
92
+
93
+ %%