@fraczak/k
Advanced tools
Comparing version 1.2.0 to 1.2.1
{ | ||
"name": "@fraczak/k", | ||
"version": "1.2.0", | ||
"version": "1.2.1", | ||
"description": "k-language for JSON-like data transformation", | ||
@@ -8,3 +8,3 @@ "main": "index.js", | ||
"prepare": "coffee -c symbol-table.coffee; jison parser.jison", | ||
"prepublishOnly": "coffee -c *.coffee; echo '#!/usr/bin/env node' | cat - k.js > k.sh; echo '#!/usr/bin/env node' | cat - repl.js > repl.sh;", | ||
"prepublishOnly": "coffee -c *.coffee; echo '#!/usr/bin/env node' | cat - k.js > k.sh; echo '#!/usr/bin/env node' | cat - repl.js > repl.sh;", | ||
"all": "coffee -c *.coffee; npm run prepare; npm run prepublishOnly", | ||
@@ -20,5 +20,5 @@ "test": "coffee -c symbol-table.coffee; jison parser.jison; coffee test.coffee" | ||
"devDependencies": { | ||
"jison": "^0.4.18", | ||
"coffeescript": "^2.7.0" | ||
"coffeescript": "^2.7.0", | ||
"jison": "^0.4.18" | ||
} | ||
} |
380
README.md
@@ -1,16 +0,24 @@ | ||
# k-language | ||
# k-language | ||
npm install @fraczak/k | ||
npm install @fraczak/k | ||
Another JSON transformation notation. A `k`-expression | ||
(script) defines a __partial function__, i.e., a function which, in | ||
general, is not defined for every input. `k`-expressions can be | ||
combined to build other `k`-expressions in three ways: | ||
From javascript: | ||
var k = require("@fraczak/k"), | ||
fn = k.compile("<.name,.nom,'?'>"); | ||
console.log([{name:"x"},{nom:"y"},{}].map(fn)); | ||
// returns: [ "x", "y", "?" ] | ||
Another JSON transformation notation. A `k`-expression (script) | ||
defines a __partial function__, i.e., a function which may fail | ||
for some input. `k`-expressions can be combined to build | ||
other `k`-expressions in three ways: | ||
1. composition, e.g.: | ||
(E1 E2 E3 ...) | ||
Apart from empty composition, `()`, which defines _identity_, the paranthesis can be omitted. | ||
Apart from empty composition, `()`, which defines _identity_, the | ||
paranthesis can be omitted. | ||
2. product, e.g., vector or structure: | ||
@@ -21,86 +29,80 @@ | ||
Now, structure can also be written similarly to JSON, ie., | ||
{ e1: E1, e2: E2, e3: E3, ... } | ||
3. union (merge), e.g.: | ||
< E1, E2, E3, ... > | ||
Elementary partial functions are: | ||
1. _projection_, i.e., extracting the value of a given field or | ||
index. Examples: | ||
.x | ||
."field name" | ||
.4 | ||
The function is defined only if its argument is a structure with | ||
the field (or a vector with the index). | ||
2. _constants_, literals for Strings, Booleans, and | ||
Elementary partial functions are: | ||
4. _projection_, i.e., extracting the value of a given field or | ||
index. Examples: | ||
.x | ||
."field name" | ||
.4 | ||
The function is defined only if its argument is a structure with | ||
the field (or a vector with the index). | ||
5. _constants_, literals for Strings, Booleans, and | ||
Integers. Examples: | ||
"a string" | ||
'another "String"' | ||
123 | ||
false | ||
null | ||
3. There is a number of built-in functions: `GT`, `EQ`, `PLUS`, | ||
`TIMES`, `MINUS`, `DIV`, `CONCAT`, `toJSON`, `fromJSON`, `CONS`, `SNOC`. For example | ||
[1, 2, 3] PLUS --> integer constant function 6 | ||
[4, 4] TIMES toJSON --> string constant function "16" | ||
[3, 2] GT --> vector constant function [3, 2] | ||
[3, 4] GT --> is not defined! | ||
A more interesting example could be: | ||
< GT .0, .1> | ||
which selects the maximum element in two element vector, i.e., | ||
[3,8] < GT .0, .1 > --> 8 | ||
"a string" | ||
'another "String"' | ||
123 | ||
false | ||
null | ||
6. There is a number of built-in functions: `GT`, `EQ`, `PLUS`, | ||
`TIMES`, `MINUS`, `DIV`, `CONCAT`, `toJSON`, `fromJSON`, `CONS`, | ||
`SNOC`, `_log!`, ... For example: | ||
[1, 2, 3] PLUS --> integer constant function 6 | ||
[4, 4] TIMES toJSON --> string constant function "16" | ||
[3, 2] GT --> vector constant function [3, 2] | ||
[3, 4] GT --> is not defined! | ||
A more interesting example could be: | ||
< GT .0, .1> | ||
which selects the maximum element in two element vector, i.e., | ||
[3,8] < GT .0, .1 > --> 8 | ||
## User defined functions | ||
`k`-expression can be prefixed by function definitions. E.g.: | ||
`k`-expression can be prefixed by function definitions. E.g.: | ||
dec = [(),1] MINUS; | ||
factorial = < | ||
[1,()] GT 1, | ||
[dec factorial, ()] TIMES | ||
>; | ||
{ () x, factorial "x!" } | ||
dec = [(),1] MINUS; | ||
factorial = < | ||
[(),1] EQ 1, | ||
[dec factorial, ()] TIMES | ||
>; | ||
{ () x, < factorial, "not defined" > "x!" } | ||
Another example could be finding the biggest (max) value in a vector: | ||
max = < | ||
SNOC -- [x0, x1, x2, ...] -> [x0, [x1, x2, ...]] | ||
[.0, .1 max] -- [x0, [x1, x2, ...]] -> [x0, max(x1,x2,...)], i.e., recursive call | ||
<GT.0,.1>, -- if x0 > max(x1,x2,...) then x0 else max(x1,x2,...) | ||
-- when SNOC is not defined, e.g. the argument is a singleton vector [x0] | ||
.0 -- [x0] -> x0, | ||
>; | ||
max | ||
max = < | ||
SNOC -- [x0, x1, x2, ...] -> [x0, [x1, x2, ...]] | ||
[.0, .1 max] -- [x0, [x1, x2, ...]] -> [x0, max(x1,x2,...)], i.e., recursive call | ||
<GT.0,.1>, -- if x0 > max(x1,x2,...) then x0 else max(x1,x2,...) | ||
-- when SNOC is not defined, e.g. the argument is a singleton vector [x0] | ||
.0 -- [x0] -> x0, | ||
>; | ||
max | ||
## Value encodings (also called _codes_) | ||
## Value encodings (_codes_) | ||
There are three predefined value encodings: `int`, `string`, and `bool`. The language | ||
supports `code`-expressions: | ||
There are three predefined value encodings: `int`, `string`, and | ||
`bool`. The language supports `code`-expressions: | ||
* product, e.g., `{int x, int y, bool flag}` (in JSON-like syntax `{x: int, y: int, flag: bool}`) | ||
* disjoint union, e.g., `<{} true, {} false>` (in JSON-like syntax `<true: {}, false: {}>`) | ||
* vector, e.g., `[ int ]` (all elements of the vector use the same encoding) | ||
* product, e.g., `{int x, int y, bool flag}` | ||
* disjoint union, e.g., `<{} true, {} false>` | ||
* vector, e.g., `[ int ]` (all elements of the vector use the same | ||
encoding) | ||
One can define recursive codes. E.g.: | ||
$ tree = <string leaf, {tree left, tree right} tree>; | ||
tree = <string leaf, {tree left, tree right} tree>; | ||
or, the same in JSON-like notation: | ||
$ tree = <leaf: string, tree: {left: tree, right: tree}>; | ||
Each _code_ definition starts with a `$`. | ||
@@ -112,12 +114,11 @@ The above example defines new code called _tree_. | ||
$ tree = <string leaf, {tree left, tree right} tree>; | ||
inc = [(),1] PLUS; | ||
max = <GT .0, .1>; | ||
height = $ tree < | ||
.leaf 0, | ||
.tree [.left height, .right height] max inc | ||
> $ int; | ||
height | ||
$ tree = <string leaf, {tree left, tree right} tree>; | ||
inc = [(),1] PLUS; | ||
max = <GT .0, .1>; | ||
height = $ tree < | ||
.leaf 0, | ||
.tree [.left height, .right height] max inc | ||
> $ int; | ||
height | ||
## Command line script | ||
@@ -128,6 +129,6 @@ | ||
$ ./node_modules/.bin/k | ||
... errors ... | ||
Usage: ./node_modules/.bin/k ( k-expr | -k k-file) [ -1 ] [ json-file ] | ||
E.g., cat '{"a": 10}' | ./node_modules/.bin/k '[(),()]' | ||
> ./node_modules/.bin/k | ||
... errors ... | ||
Usage: ./node_modules/.bin/k ( k-expr | -k k-file) [ -1 ] [ json-file ] | ||
E.g., cat '{"a": 10}' | ./node_modules/.bin/k '[(),()]' | ||
@@ -138,112 +139,139 @@ ### Examples | ||
$ echo '{"x": 12, "y": 13}' | ./k.coffee '{ <.x, "no x"> x, () input}' | ||
{"x":12,"input":{"x":12,"y":13}} | ||
> echo '{"x": 12, "y": 13}' | ./k.coffee '{ <.x, "no x"> x, () input}' | ||
{"x":12,"input":{"x":12,"y":13}} | ||
2. By providing only `k`-expression, the script will compile the | ||
`k`-expression and apply the generated function to the `stdin`, line by line: | ||
`k`-expression and apply the generated function to the `stdin`, | ||
line by line: | ||
$ ./node_modules/.bin/k '<["x=",.x," & y=",.y],["only x=",.x],["only y=",.y],["no x nor y"]>{CONCAT "x&y"}' | ||
{"y": 123, "x": 432,"others": "..."} --> {"x&y":"x=432 & y=123"} | ||
{"x":987} --> {"x&y":"only x=987"} | ||
{"z":123} --> {"x&y":"no x nor y"} | ||
^D - to interrupt | ||
> ./node_modules/.bin/k '<["x=",.x," & y=",.y],["only x=",.x],["only y=",.y],["no x nor y"]>{CONCAT "x&y"}' | ||
{"y": 123, "x": 432,"others": "..."} --> {"x&y":"x=432 & y=123"} | ||
{"x":987} --> {"x&y":"only x=987"} | ||
{"z":123} --> {"x&y":"no x nor y"} | ||
^D - to interrupt | ||
3. If the `k`-expression is long, it can be put in a file, e.g.: | ||
$ cat test.k | ||
--------- comments start by #, --, or // ---------------------------------- | ||
< -- merge of 4 partial functions... | ||
["x=", .x, " & y=", .y], -- produces a vector of 4 values, if fields 'x' and 'y' are present | ||
["only x=", .x], -- produces a pair '["only x=", "value-of-x"]', for input like {"x":"value-of-x"} | ||
-- it is defined only if field 'x' is present | ||
["only y=", .y], | ||
["no x nor y"] -- defined for all input, returns always the same one element vector | ||
> | ||
-- one of the string vectors is passed to the following partial function, | ||
-- which produces a record (map) with one field "x&y", whose value is the | ||
-- result of concatenating elements of the passed in vector | ||
{ CONCAT "x&y" } | ||
------------------------------------------------------------------------------ | ||
We can use it by: | ||
$ ./node_modules/.bin/k -k test.k | ||
If we want to read `json` objects from a file, e.g., `my-objects.json`, we do | ||
$ ./node_modules/.bin/k -k test.k my-objects.json | ||
{"x&y":"x=432 & y=123"} | ||
{"x&y":"onlyx=987"} | ||
{"x&y":"no x nor y"} | ||
where: | ||
$ cat my-objects.json | ||
#################################################### | ||
# empty lines and lines starting with # are ignored | ||
{"y": 123, "x": 432,"others": "..."} | ||
{"x":987} | ||
{"z":123} | ||
#################################################### | ||
# Using from `javascript` | ||
var k = require("k"); | ||
> cat test.k | ||
--------- comments start by #, --, or // ---------------------------------- | ||
< -- merge of 4 partial functions... | ||
["x=", .x, " & y=", .y], -- produces a vector of 4 values, if fields 'x' and 'y' are present | ||
["only x=", .x], -- produces a pair '["only x=", "value-of-x"]', for input like {"x":"value-of-x"} | ||
-- it is defined only if field 'x' is present | ||
["only y=", .y], | ||
["no x nor y"] -- defined for all input, returns always the same one element vector | ||
> | ||
-- one of the string vectors is passed to the following partial function, | ||
-- which produces a record (map) with one field "x&y", whose value is the | ||
-- result of concatenating elements of the passed in vector | ||
{ CONCAT "x&y" } | ||
------------------------------------------------------------------------------ | ||
k_expression = '()'; | ||
k.run(k_expression,"ANYTHING..."); | ||
// RETURNS: "ANYTHING..." | ||
We can use it by: | ||
k_expression = '{"ala" name, 23 age}'; | ||
k.run(k_expression,"ANYTHING..."); | ||
// RETURNS: {"name":"ala","age":23} | ||
> ./node_modules/.bin/k -k test.k | ||
k_expression = '[.year, .age]'; | ||
k.run(k_expression,{"year":2002,"age":19}); | ||
// RETURNS: [2002,19] | ||
If we want to read `json` objects from a file, e.g., `my-objects.json`, we do | ||
k_expression = '[(), ()]'; | ||
k.run(k_expression,"duplicate me"); | ||
// RETURNS: ["duplicate me","duplicate me"] | ||
> ./node_modules/.bin/k -k test.k my-objects.json | ||
{"x&y":"x=432 & y=123"} | ||
{"x&y":"onlyx=987"} | ||
{"x&y":"no x nor y"} | ||
k_expression = '[[[()]]]'; | ||
k.run(k_expression,"nesting"); | ||
// RETURNS: [[["nesting"]]] | ||
where: | ||
k_expression = '[[()]] {() nested, .0.0 val}'; | ||
k.run(k_expression,"nesting and accessing"); | ||
// RETURNS: {"nested":[["nesting and accessing"]],"val":"nesting and accessing"} | ||
> cat my-objects.json | ||
#################################################### | ||
# empty lines and lines starting with # are ignored | ||
{"y": 123, "x": 432,"others": "..."} | ||
{"x":987} | ||
{"z":123} | ||
#################################################### | ||
k_expression = '0000'; | ||
k.run(k_expression,{"test":"parse integer"}); | ||
// RETURNS: 0 | ||
### k-REPL (Read-Evaluate-Print Loop) | ||
k_expression = '[.y,.x] PLUS'; | ||
k.run(k_expression,{"x":3,"y":4}); | ||
// RETURNS: 7 | ||
Also there is a REPL, `./node_modules/.bin/k-repl`, which acts like a toy | ||
shell for the language. E.g.: | ||
var k_fn = k.compile('{.name nom, <[.age, 18] GT .0, [.age, 12] GT "ado", "enfant"> age}'); | ||
> ./node_modules/.bin/k-repl | ||
{'a' a, 'b' b} toJSON | ||
=> "{\"a\":\"a\",\"b\":\"b\"}" | ||
{"a" a} toJSON fromJSON | ||
=> {"a":"a"} | ||
inc = [(),1] PLUS; 1 inc inc | ||
=> 3 | ||
inc inc inc inc | ||
=> 7 | ||
k_fn({"age":23,"name":"Emily"}); | ||
// RETURNS: {"nom":"Emily","age":23} | ||
## Using from `javascript` | ||
k_fn({"age":16,"name":"Katrina"}); | ||
// RETURNS: {"nom":"Katrina","age":"ado"} | ||
var k = require("k"); | ||
k_fn({"age":2,"name":"Mark"}); | ||
// RETURNS: {"nom":"Mark","age":"enfant"} | ||
k_expression = '()'; | ||
k.run(k_expression,"ANYTHING..."); | ||
// RETURNS: "ANYTHING..." | ||
var k_fn = k.compile('$t = < i: int, t: [ t ] > ; <$t, $int>'); | ||
k_expression = '{"ala" name, 23 age}'; | ||
k.run(k_expression,"ANYTHING..."); | ||
// RETURNS: {"name":"ala","age":23} | ||
k_fn(1); | ||
// RETURNS: 1 | ||
k_expression = '[.year, .age]'; | ||
k.run(k_expression,{"year":2002,"age":19}); | ||
// RETURNS: [2002,19] | ||
k_fn({"i":1}); | ||
// RETURNS: {"i":1} | ||
k_expression = '[(), ()]'; | ||
k.run(k_expression,"duplicate me"); | ||
// RETURNS: ["duplicate me","duplicate me"] | ||
k_fn([{"i":2},{"i":3},{"t":[]}]); | ||
// RETURNS: undefined | ||
k_expression = '[[[()]]]'; | ||
k.run(k_expression,"nesting"); | ||
// RETURNS: [[["nesting"]]] | ||
k_fn({"t":[{"i":2},{"i":3},{"t":[]}]}); | ||
// RETURNS: {"t":[{"i":2},{"i":3},{"t":[]}]} | ||
k_expression = '[[()]] {() nested, .0.0 val}'; | ||
k.run(k_expression,"nesting and accessing"); | ||
// RETURNS: {"nested":[["nesting and accessing"]],"val":"nesting and accessing"} | ||
k_expression = '0000'; | ||
k.run(k_expression,{"test":"parse integer"}); | ||
// RETURNS: 0 | ||
k_expression = '[.y,.x] PLUS'; | ||
k.run(k_expression,{"x":3,"y":4}); | ||
// RETURNS: 7 | ||
var k_fn = k.compile('{.name nom, <[.age, 18] GT .0, [.age, 12] GT "ado", "enfant"> age}'); | ||
k_fn({"age":23,"name":"Emily"}); | ||
// RETURNS: {"nom":"Emily","age":23} | ||
k_fn({"age":16,"name":"Katrina"}); | ||
// RETURNS: {"nom":"Katrina","age":"ado"} | ||
k_fn({"age":2,"name":"Mark"}); | ||
// RETURNS: {"nom":"Mark","age":"enfant"} | ||
var k_fn = k.compile('$t = < i: int, t: [ t ] > ; <$t, $int>'); | ||
k_fn(1); | ||
// RETURNS: 1 | ||
k_fn({"i":1}); | ||
// RETURNS: {"i":1} | ||
k_fn([{"i":2},{"i":3},{"t":[]}]); | ||
// RETURNS: undefined | ||
k_fn({"t":[{"i":2},{"i":3},{"t":[]}]}); | ||
// RETURNS: {"t":[{"i":2},{"i":3},{"t":[]}]} | ||
var k_fn = k.compile('$ < < [ int ] ints, [ bool ] bools > list, string None>'); | ||
k_fn({"None":"None"}); | ||
// RETURNS: {"None":"None"} | ||
k_fn({"list":{"ints":[]}}); | ||
// RETURNS: {"list":{"ints":[]}} | ||
k_fn({"list":{"ints":[1,2,3]}}); | ||
// RETURNS: {"list":{"ints":[1,2,3]}} |
149
run.js
@@ -123,7 +123,13 @@ // Generated by CoffeeScript 2.7.0 | ||
verify = function(code, value) { | ||
// representatives = run.defs.representatives | ||
// defCodes = JSON.stringify run.defs.codes | ||
// console.log {code,value, representatives, defCodes} | ||
if (code == null) { | ||
// representatives = run.defs.representatives | ||
// defCodes = JSON.stringify run.defs.codes | ||
// console.log {code,value, representatives, defCodes} | ||
return false; | ||
} | ||
switch (code.code) { | ||
case "vector": | ||
if (!Array.isArray(value)) { | ||
return false; | ||
} | ||
return value.every(function(x) { | ||
@@ -133,2 +139,5 @@ return verify(code.vector, x); | ||
case "product": | ||
if ("object" !== typeof value) { | ||
return false; | ||
} | ||
return (function(fields) { | ||
@@ -143,2 +152,5 @@ if (fields.length !== Object.keys(code.product).length) { | ||
case "union": | ||
if ("object" !== typeof value) { | ||
return false; | ||
} | ||
return (function(fields) { | ||
@@ -161,3 +173,4 @@ if (fields.length !== 1) { | ||
run = function(exp, value) { | ||
var e, i, len, ref, ref1, result; | ||
"use strict"; | ||
var defn, e, i, j, k, label, len, len1, len2, r, ref, ref1, ref2, ref3, result; | ||
if (value === void 0) { | ||
@@ -167,74 +180,64 @@ // console.log {exp,value} | ||
} | ||
try { | ||
switch (exp.op) { | ||
case "code": | ||
if (verify(exp.code, value)) { | ||
return value; | ||
switch (exp.op) { | ||
case "code": | ||
if (verify(exp.code, value)) { | ||
return value; | ||
} | ||
break; | ||
case "identity": | ||
return value; | ||
case "str": | ||
case "int": | ||
return exp[exp.op]; | ||
case "ref": | ||
defn = (ref = run.defs.rels[exp.ref]) != null ? ref[0] : void 0; | ||
if (defn != null) { | ||
return run(defn, value); | ||
} | ||
return builtin[exp.ref](value); | ||
case "dot": | ||
return value[exp.dot]; | ||
case "comp": | ||
return exp.comp.reduce(function(value, exp) { | ||
if (value !== void 0) { | ||
return run(exp, value); | ||
} | ||
break; | ||
case "identity": | ||
return value; | ||
case "str": | ||
case "int": | ||
return exp[exp.op]; | ||
case "ref": | ||
return (function(defn) { | ||
if (defn != null) { | ||
return run(defn, value); | ||
} else { | ||
return (function(value) { | ||
if (value === void 0) { | ||
throw new Error("Undefined"); | ||
} | ||
return value; | ||
})(builtin[exp.ref](value)); | ||
} | ||
})((ref = run.defs.rels[exp.ref]) != null ? ref[0] : void 0); | ||
case "dot": | ||
return value[exp.dot]; | ||
case "comp": | ||
return exp.comp.reduce(function(value, exp) { | ||
value = run(exp, value); | ||
if (value === void 0) { | ||
throw new Error("Undefined"); | ||
} | ||
return value; | ||
}, value); | ||
case "vector": | ||
return exp.vector.map(function(exp) { | ||
var result; | ||
result = run(exp, value); | ||
if (result === void 0) { | ||
throw new Error("Undefined"); | ||
} | ||
}, value); | ||
case "union": | ||
ref1 = exp.union; | ||
for (i = 0, len = ref1.length; i < len; i++) { | ||
e = ref1[i]; | ||
result = run(e, value); | ||
if (result !== void 0) { | ||
return result; | ||
}); | ||
case "union": | ||
ref1 = exp.union; | ||
for (i = 0, len = ref1.length; i < len; i++) { | ||
e = ref1[i]; | ||
result = (function() { | ||
try { | ||
return run(e, value); | ||
} catch (error) {} | ||
})(); | ||
if (result !== void 0) { | ||
return result; | ||
} | ||
} | ||
return void 0; | ||
case "product": | ||
return exp.product.reduce(function(result, {label, exp}) { | ||
return (function(value) { | ||
if (value === void 0) { | ||
throw new Error("Undefined"); | ||
} | ||
result[label] = value; | ||
return result; | ||
})(run(exp, value)); | ||
}, {}); | ||
default: | ||
return console.log(exp.op); | ||
} | ||
} catch (error) {} | ||
} | ||
return void 0; | ||
case "vector": | ||
result = []; | ||
ref2 = exp.vector; | ||
for (j = 0, len1 = ref2.length; j < len1; j++) { | ||
e = ref2[j]; | ||
r = run(e, value); | ||
if (r === void 0) { | ||
return; | ||
} | ||
result.push(r); | ||
} | ||
return result; | ||
case "product": | ||
result = {}; | ||
ref3 = exp.product; | ||
for (k = 0, len2 = ref3.length; k < len2; k++) { | ||
({label, exp} = ref3[k]); | ||
r = run(exp, value); | ||
if (r === void 0) { | ||
return; | ||
} | ||
result[label] = r; | ||
} | ||
return result; | ||
default: | ||
return console.error(exp.op); | ||
} | ||
}; | ||
@@ -241,0 +244,0 @@ |
Sorry, the diff of this file is not supported yet
1547
274
86569
23