@cnrs/hel 0.2.0 → 0.3.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/package.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "https://medium.com/sapioit/why-having-3-numbers-in-the-version-name-is-bad-92fc1f6bc73c",
29
29
  "https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e"
30
30
  ],
31
- "version": "0.2.0",
31
+ "version": "0.3.0",
32
32
  "keywords": [
33
33
  "hera",
34
34
  "hecate",
package/src/ast2str.js ADDED
@@ -0,0 +1,105 @@
1
+
2
+
3
+ export const KEYS = {
4
+ 'pid': property,
5
+ 'eid': entity,
6
+ 'aid': attribute,
7
+ 'not': not,
8
+ 'and': and,
9
+ 'or': or,
10
+ 'operator': relation,
11
+ };
12
+
13
+ function visit(ast, parentheses=false) {
14
+ for (const [key, visit_] of Object.entries(KEYS)) {
15
+ if (key in ast) {
16
+ return visit_(ast, parentheses);
17
+ }
18
+ }
19
+ throw new Error(`${dump} is invalid`);
20
+ }
21
+
22
+ function property(ast) {
23
+ validate(ast, ['pid', ]);
24
+ return `pid ${ast.pid}`;
25
+ }
26
+
27
+ function entity(ast) {
28
+ validate(ast, ['eid', ]);
29
+ return `eid ${ast.eid}`;
30
+ }
31
+
32
+ function attribute(ast) {
33
+ validate(ast, ['aid', ]);
34
+ return `aid ${ast.aid}`;
35
+ }
36
+
37
+ function not(ast) {
38
+ validate(ast, ['not', ]);
39
+ return `!(${visit(ast.not)})`;
40
+ }
41
+
42
+ function and(ast) {
43
+ validate(ast, ['and', ]);
44
+ const operands = [];
45
+ for (const operand of ast.and) {
46
+ operands.push(visit(operand, true));
47
+ }
48
+ return `${operands.join(' & ')}`;
49
+ }
50
+
51
+ function or(ast, parentheses=false) {
52
+ validate(ast, ['or', ]);
53
+ const operands = [];
54
+ for (const operand of ast.or) {
55
+ operands.push(visit(operand));
56
+ }
57
+ const result = `${operands.join(' | ')}`;
58
+ return (parentheses && (result.length > 0)) ? `(${result})` : result;
59
+ }
60
+
61
+ function relation(ast) {
62
+ validate(ast, ['left', 'operator', 'right']);
63
+ const left = atom(ast.left);
64
+ const right = atom(ast.right);
65
+ return `${left} ${ast.operator} ${right}`
66
+ }
67
+
68
+ function atom(ast) {
69
+ if (typeof ast === 'object') {
70
+ return isPropertyOrAttribute(ast);
71
+ }
72
+ if (typeof ast === 'string') {
73
+ return `"${ast}"`;
74
+ }
75
+ return ast.toString();
76
+ }
77
+
78
+ function isPropertyOrAttribute(ast) {
79
+ try { return property(ast); }
80
+ catch (error) { /* it's probably an attribute ... */ }
81
+ try { return attribute(ast); }
82
+ catch (error) { /* now we have a problem ! */ }
83
+ const dump = JSON.stringify(ast);
84
+ throw new Error(`${dump} is invalid (expected key: 'aid' or 'pid')`);
85
+ }
86
+
87
+
88
+ function validate(ast, keys) {
89
+ for (const key of keys) {
90
+ if (!(key in ast)) {
91
+ const dump = JSON.stringify(ast);
92
+ throw new Error(`Missing key in ${dump} (expected: '${key}')`);
93
+ }
94
+ }
95
+ if (Object.keys(ast).length > keys.length) {
96
+ const dump = JSON.stringify(ast);
97
+ const mine = keys.join(',')
98
+ throw new Error(`Unknown keys in ${dump} (expected only: ${mine})`);
99
+ }
100
+ }
101
+
102
+
103
+ export function toString(ast) {
104
+ return visit(input);
105
+ }
package/src/grammar.js CHANGED
@@ -36,6 +36,7 @@ export class Parser extends CstParser {
36
36
  this.SUBRULE(this.statement);
37
37
  this.CONSUME(tokens.P_RIGHT);
38
38
  }},
39
+ { ALT: () => this.SUBRULE(this.entity) },
39
40
  ]);
40
41
  });
41
42
 
@@ -52,7 +53,8 @@ export class Parser extends CstParser {
52
53
  this.OR([
53
54
  { ALT: () => this.CONSUME(tokens.Integer) },
54
55
  { ALT: () => this.CONSUME(tokens.String) },
55
- { ALT: () => this.CONSUME(tokens.Identifier) },
56
+ { ALT: () => this.SUBRULE(this.attribute) },
57
+ { ALT: () => this.SUBRULE(this.property) },
56
58
  ]);
57
59
  });
58
60
 
@@ -67,6 +69,22 @@ export class Parser extends CstParser {
67
69
  ]);
68
70
  });
69
71
 
72
+ this.RULE('entity', () => {
73
+ this.CONSUME(tokens.EID);
74
+ this.CONSUME(tokens.Identifier);
75
+ });
76
+
77
+ this.RULE('attribute', () => {
78
+ this.CONSUME(tokens.AID);
79
+ this.CONSUME(tokens.Identifier);
80
+ });
81
+
82
+ this.RULE('property', () => {
83
+ this.OPTION(() => { this.CONSUME(tokens.PID); });
84
+ this.CONSUME(tokens.Identifier);
85
+ });
86
+
87
+
70
88
  // very important to call this after all the rules have been defined.
71
89
  // otherwise the parser may not work correctly as it will lack information
72
90
  // derived during the self analysis phase.
package/src/str2ast.js CHANGED
@@ -52,6 +52,9 @@ class StringToAstVisitor extends CstVisitor {
52
52
  not: this.visit(context.statement),
53
53
  };
54
54
  } // else
55
+ if (context.entity) {
56
+ return this.visit(context.entity);
57
+ } // else
55
58
  return this.visit(context.statement);
56
59
  }
57
60
 
@@ -65,9 +68,10 @@ class StringToAstVisitor extends CstVisitor {
65
68
 
66
69
  atom(context) {
67
70
  // note: these visitor methods will return a string (xxx.image)
71
+ if (context.attribute) return this.visit(context.attribute);
72
+ if (context.property) return this.visit(context.property);
68
73
  if (context.Integer) return parseInt(context.Integer[0].image);
69
- if (context.String) return context.String[0].image;
70
- return context.Identifier[0].image;
74
+ return context.String[0].image;
71
75
  }
72
76
 
73
77
  relationalOperator(context) {
@@ -79,6 +83,19 @@ class StringToAstVisitor extends CstVisitor {
79
83
  if (context.NotEqual) return context.NotEqual[0].image;
80
84
  return context.Equal[0].image;
81
85
  }
86
+
87
+ property(context) {
88
+ return { pid: context.Identifier[0].image };
89
+ }
90
+
91
+ attribute(context) {
92
+ return { aid: context.Identifier[0].image };
93
+ }
94
+
95
+ entity(context) {
96
+ return { eid: context.Identifier[0].image };
97
+ }
98
+
82
99
  }
83
100
 
84
101
  const visitor = new StringToAstVisitor(); // this is stateless, so Singleton
package/src/str2fun.js CHANGED
@@ -5,9 +5,10 @@ import { Parser } from './grammar.js';
5
5
  const parser = new Parser(); // CST output is enabled by default
6
6
  const CstVisitor = parser.getBaseCstVisitorConstructor();
7
7
 
8
- function getMetadata(item, pid) { // TODO use heimdall.getMetadata(item, ...)
8
+ function getMetadata(item, aid, pid) { // TODO? use heimdall.getMetadata(item, ...)
9
9
  for (const meta of item['metadata']) {
10
- if (meta['pid'] == pid) return meta['value'];
10
+ if (aid != null && meta['aid'] == aid) return meta['value'];
11
+ if (pid != null && meta['pid'] == pid) return meta['value'];
11
12
  }
12
13
  return null;
13
14
  }
@@ -69,6 +70,9 @@ class StringToFunctionVisitor extends CstVisitor {
69
70
  if (context.Not) {
70
71
  return (item) => !this.visit(context.statement)(item);
71
72
  } // else
73
+ if (context.entity) {
74
+ return this.visit(context.entity);
75
+ } // else
72
76
  return this.visit(context.statement);
73
77
  }
74
78
 
@@ -79,8 +83,9 @@ class StringToFunctionVisitor extends CstVisitor {
79
83
  const right = this.visit(context.right[0]);
80
84
  return function(item) {
81
85
  if (typeof left === 'object') {
82
- const pid = left['pid'];
83
- const value = getMetadata(item, pid); // TODO what if null here ?
86
+ const aid = ('aid' in left ? left['aid'] : null);
87
+ const pid = ('pid' in left ? left['pid'] : null);
88
+ const value = getMetadata(item, aid, pid); // TODO what if null here ?
84
89
  return filter(value, right);
85
90
  }
86
91
  return filter(left, right);
@@ -88,14 +93,14 @@ class StringToFunctionVisitor extends CstVisitor {
88
93
  }
89
94
 
90
95
  atom(context) {
91
- // note: these visitor methods will return a string (xxx.image)
96
+ if (context.attribute) return this.visit(context.attribute);
97
+ if (context.property) return this.visit(context.property);
92
98
  if (context.Integer) return parseInt(context.Integer[0].image);
93
- if (context.String) return context.String[0].image;
94
- return { pid: context.Identifier[0].image };
99
+ return context.String[0].image;
95
100
  }
96
101
 
97
102
  relationalOperator(context) {
98
- // note: these visitor methods will return a function returnung a boolean
103
+ // note: these visitor methods will return a function returning a boolean
99
104
  if (context.Greater) return (x, y) => x > y;
100
105
  if (context.GreaterOrEqual) return (x, y) => x >= y;
101
106
  if (context.Lesser) return (x, y) => x < y;
@@ -103,6 +108,22 @@ class StringToFunctionVisitor extends CstVisitor {
103
108
  if (context.NotEqual) return (x, y) => x != y;
104
109
  return (x, y) => x == y;
105
110
  }
111
+
112
+ property(context) {
113
+ return { pid: context.Identifier[0].image };
114
+ }
115
+
116
+ attribute(context) {
117
+ return { aid: context.Identifier[0].image };
118
+ }
119
+
120
+ entity(context) {
121
+ return function(item) {
122
+ const eid = context.Identifier[0].image;
123
+ return item['eid'] == eid;
124
+ };
125
+ }
126
+
106
127
  }
107
128
 
108
129
  const visitor = new StringToFunctionVisitor(); // this is stateless, so Singleton
package/src/tokens.js CHANGED
@@ -87,6 +87,21 @@ export const logic = {
87
87
  export const P_LEFT = createToken({ name: 'PLeft', pattern: /\(/ });
88
88
  export const P_RIGHT = createToken({ name: 'PRight', pattern: /\)/ });
89
89
 
90
+ // KEYWORDS
91
+ //////////////
92
+ export const PID = createToken({
93
+ name: 'PropertyID', pattern: /pid/,
94
+ longer_alt: Identifier,
95
+ });
96
+ export const EID = createToken({
97
+ name: 'EntityID', pattern: /eid/,
98
+ longer_alt: Identifier,
99
+ });
100
+ export const AID = createToken({
101
+ name: 'AttributeID', pattern: /aid/,
102
+ longer_alt: Identifier,
103
+ });
104
+
90
105
  // WHITESPACE
91
106
  ////////////////
92
107
  export const WhiteSpace = createToken({
@@ -97,7 +112,7 @@ export const WhiteSpace = createToken({
97
112
 
98
113
  // /!\ order of tokens is important ! /!\
99
114
  // for example, keywords could never be matched if declared after Identifier,
100
- // or '!=' (NOT_EQUAL) could never be matched if declared after '!'I (NOT)
115
+ // or '!=' (NOT_EQUAL) could never be matched if declared after '!' (NOT)
101
116
  export const TOKENS = [
102
117
  WhiteSpace, // whitespace appears first to improve lexer performance
103
118
  // >>> START relational operators
@@ -110,6 +125,11 @@ export const TOKENS = [
110
125
  // <<< END relational operators
111
126
  P_LEFT,
112
127
  P_RIGHT,
128
+ // >>> START keywords
129
+ PID,
130
+ EID,
131
+ AID,
132
+ // <<< END keywords
113
133
  // >>> START boolean operators
114
134
  NOT,
115
135
  AND,