Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

jora

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jora - npm Package Compare versions

Comparing version 1.0.0-alpha.6 to 1.0.0-alpha.7

src/buildin.js

10

CHANGELOG.md

@@ -0,1 +1,11 @@

## 1.0.0-alpha.7
- Disallowed a whitespace between a dot and a bracket in `.[`, `.(` and `..(` tokens
- Changed filter (i.e. `.[]` or `.filter()`) behaviour for a non-array value to return a value itself when expression is truthy or `undefined` otherwise
- Changed semanthic `jora(query, methods?, debug?)` -> `jora(query, options?)` where `options` is `{ methods?, debug? }`
- Added stat mode (turns on by `stat` option, i.e. `jora(query, { stat: true })`) to return a query stat interface (an object with `stat()` and `suggestion()` methods) instead of resulting data
- Added tolerant parse mode (turns on by `tolerant` option, i.e. `jora(query, { tolerant: true })`) to supress parsing errors when possible
- Added library `version` to export
- Fixed parser edge cases for division (`/`) and regexp
## 1.0.0-alpha.6 (December 7, 2018)

@@ -2,0 +12,0 @@

7

package.json
{
"name": "jora",
"version": "1.0.0-alpha.6",
"version": "1.0.0-alpha.7",
"description": "JavaScript object query engine",

@@ -28,4 +28,4 @@ "keywords": [],

"test": "mocha --reporter progress",
"build": "npm run build:parser && browserify --standalone jora src/index.js > dist/jora.js && uglifyjs dist/jora.js --compress --mangle -o dist/jora.min.js",
"build:parser": "node -e \"console.log(require('./src/parser').generateModule() + ';module.exports=parser;');\" > dist/parser.js",
"build": "npm run build:parser && browserify -t package-json-versionify --standalone jora src/index.js > dist/jora.js && uglifyjs dist/jora.js --compress --mangle -o dist/jora.min.js",
"build:parser": "node -e \"var { strict, tolerant } = require('./src/parser');console.log(strict.generateModule({ moduleName: 'strictParser' }) + '\\x0a' + tolerant.generateModule({ moduleName: 'tolerantParser' }) + '\\x0amodule.exports = strictParser;\\x0astrictParser.strict = strictParser;\\x0astrictParser.tolerant = tolerantParser;');\" > dist/parser.js",
"prepublishOnly": "npm run build",

@@ -45,2 +45,3 @@ "coverage": "istanbul cover _mocha -- -R min",

"mocha": "^5.2.0",
"package-json-versionify": "^1.0.4",
"uglify-es": "^3.3.9"

@@ -47,0 +48,0 @@ },

@@ -20,3 +20,5 @@ # Jora

const query = jora('foo.myMethod()', {
myMethod(current) { /* do something and return a new value */ }
methods: {
myMethod(current) { /* do something and return a new value */ }
}
});

@@ -28,2 +30,32 @@

Options:
- methods
Type: `Object`
Default: `undefined`
Additional methods for using in query passed as an object, where a key is a method name and a value is a function to perform an action. It can override build-in methods.
- debug
Type: `Boolean`
Default: `false`
Enables debug output.
- tolerant
Type: `Boolean`
Default: `false`
Enables tolerant parsing mode. This mode supresses parsing errors when possible.
- stat
Type: `Boolean`
Default: `false`
Enables stat mode. When mode is enabled a query stat interface is returning instead of resulting data.
## Syntax

@@ -36,3 +68,3 @@

42<br>4.222<br>-12.34e56 | Numbers
"string"<br>'string' | A string
"string"<br>'string' | Strings
/regexp/<br>/regexp/i | A JavaScript regexp, only `i` flag supported

@@ -42,4 +74,12 @@ { } | Object initializer/literal syntax. You can use spread operator `...`, e.g. `{ a: 1, ..., ...foo, ...bar }` (`...` with no expression on right side the same as `...$`)

< > | A function<br>NOTE: Syntax will be changed
symbol<br>'sym \'bol!' | A sequence of chars that matches to `[a-zA-Z_][a-zA-Z_$0-9]*`, otherwise it must be enclosed in quotes
### Keywords
The follow keyword can be used as in JavaScript:
- `true`
- `false`
- `null`
- `undefined`
### Comparisons

@@ -66,3 +106,3 @@

not x | Boolean not (a `!` in JS)
x ? y : z | If boolean x, value y, else z
x ? y : z | If `x` is truthy than return `y` else return `z`
( x ) | Explicity operator precedence

@@ -72,3 +112,3 @@

jora | Description
Jora | Description
--- | ---

@@ -81,16 +121,42 @@ x + y | Add

### Queries
### Special variables
jora | Description
Jora | Description
--- | ---
@ | The root data object
$ | The current data object
$ | The current data object, depends on scope
\# | The context
### A block
A block contains of a definition list (should comes first) and an expression. Both are optional. When an expression is empty a current value (i.e. `$`) returns.
The syntax of definition (white spaces between any part are optional):
```
$ SYMBOL ;
$ SYMBOL : expression ;
```
For example:
```
$foo:123; // Define `$foo` variable
$bar; // The same as `$bar:$.bar;` or `$a:bar;`
$baz: $foo + $bar; // Variables can be used inside an expression after its definition
```
A block creates a new scope. Variables can't be redefined in the same and nested scopes, otherwise it cause to error.
### Path chaining
jora | Description
--- | ---
SYMBOL | The same as `$.SYMBOL`
.e | Child member operator (example: `foo.bar.baz`, `#.foo.'use any symbols for name'`)
..e | Recursive descendant operator (example: `..deps`, `..(deps + dependants)`)
.[ e ] | Filter a current data. Equivalent to a `.filter(<e>)`
.( e ) | Map a current data. Equivalent to a `.map(<e>)`
.SYMBOL | Child member operator (example: `foo.bar.baz`, `#.foo['use any symbols for name']`)
..SYMBOL<br> ..( block ) | Recursive descendant operator (example: `..deps`, `..(deps + dependants)`)
.[ block ] | Filter a current data. Equivalent to a `.filter(<block>)`
.( block ) | Map a current data. Equivalent to a `.map(<block>)`
.method() | Invoke a method to current data, or each element of current data if it is an array
path[key] | Array-like notation to access properties. It works like in JS for everything with exception for arrays, where it equivalents to `array.map(e => e[key])`. Use `pick()` method to get an element by index in array.
path[e] | Array-like notation to access properties. It works like in JS for everything with exception for arrays, where it equivalents to `array.map(e => e[key])`. Use `pick()` method to get an element by index in array.

@@ -106,7 +172,7 @@ ## Build-in methods

mapToArray("key"[, "value"]) | Converts an object to an array, and store object key as "key"
pick("key") | Get a value by a key or an index. Useful for arrays, e.g. since `array[5]` applies `[5]` for each element in an array (equivalent to `array.map(e => e[5])`), `array.pick(5)` should be used instead.
pick("key")<br>pick(fn) | Get a value by a key, an index or a function. Useful for arrays, e.g. since `array[5]` applies `[5]` for each element in an array (equivalent to `array.map(e => e[5])`), `array.pick(5)` should be used instead.
size() | Returns count of keys if current data is object, otherwise returns `length` value or `0` when field is absent
sort(\<getter>) | Sort an array by a value fetched with getter
sort(\<fn>) | Sort an array by a value fetched with getter
reverse() | Reverse order of items
group(\<getter>[, \<getter>]) | Group an array items by a value fetched with first getter.
group(\<fn>[, \<fn>]) | Group an array items by a value fetched with first getter.
filter(\<fn>) | The same as `Array#filter()` in JS

@@ -113,0 +179,0 @@ map(\<fn>) | The same as `Array#map()` in JS

@@ -1,397 +0,310 @@

const parser = require('./parser');
const hasOwnProperty = Object.prototype.hasOwnProperty;
const cache = Object.create(null);
const buildin = require('./buildin');
const methods = require('./methods');
const {
strict: strictParser,
tolerant: tolerantParser
} = require('./parser');
const { addToSet, isPlainObject} = require('./utils');
const TYPE_ARRAY = 1;
const TYPE_OBJECT = 2;
const TYPE_SCALAR = 3;
const cacheStrict = new Map();
const cacheStrictStat = new Map();
const cacheTollerant = new Map();
const cacheTollerantStat = new Map();
const contextToType = {
'path': 'property',
'key': 'property',
'value': 'value',
'in-value': 'value',
'var': 'variable'
};
function noop() {}
function self(value) {
return value;
function isWhiteSpace(str, offset) {
const code = str.charCodeAt(offset);
return code === 9 || code === 10 || code === 13 || code === 32;
}
function getPropertyValue(value, property) {
return value && hasOwnProperty.call(value, property) ? value[property] : undefined;
}
function isPlainObject(value) {
return value && typeof value === 'object' && value.constructor === Object;
}
function addToSet(value, set) {
if (value !== undefined) {
if (Array.isArray(value)) {
value.forEach(item => set.add(item));
} else {
set.add(value);
function valuesToSuggestions(context, values) {
const suggestions = new Set();
const addValue = value => {
switch (typeof value) {
case 'string':
suggestions.add(JSON.stringify(value));
break;
case 'number':
suggestions.add(String(value));
break;
}
}
}
};
var buildin = Object.freeze({
type: function(value) {
if (Array.isArray(value)) {
return TYPE_ARRAY;
}
switch (context) {
case '':
case 'path':
values.forEach(value => {
if (Array.isArray(value)) {
value.forEach(item => {
if (isPlainObject(item)) {
addToSet(suggestions, Object.keys(item));
}
});
} else if (isPlainObject(value)) {
addToSet(suggestions, Object.keys(value));
}
});
break;
if (isPlainObject(value)) {
return TYPE_OBJECT;
}
case 'key':
values.forEach(value => {
if (isPlainObject(value)) {
addToSet(suggestions, Object.keys(value));
}
});
break;
return TYPE_SCALAR;
},
bool: function(data) {
switch (this.type(data)) {
case TYPE_ARRAY:
return data.length > 0;
case TYPE_OBJECT:
for (let key in data) {
if (hasOwnProperty.call(data, key)) {
return true;
}
case 'value':
values.forEach(value => {
if (Array.isArray(value)) {
value.forEach(addValue);
} else {
addValue(value);
}
return false;
});
break;
default:
return Boolean(data);
}
},
add: function(a, b) {
const typeA = this.type(a);
const typeB = this.type(b);
if (typeA !== TYPE_ARRAY) {
if (typeB === TYPE_ARRAY) {
[a, b] = [b, a];
}
}
switch (this.type(a)) {
case TYPE_ARRAY:
return [...new Set([].concat(a, b))];
case TYPE_OBJECT:
return Object.assign({}, a, b);
default:
return a + b;
}
},
sub: function(a, b) {
switch (this.type(a)) {
case TYPE_ARRAY:
const result = new Set(a);
// filter b items from a
if (Array.isArray(b)) {
b.forEach(item => result.delete(item));
case 'in-value':
values.forEach(value => {
if (Array.isArray(value)) {
value.forEach(addValue);
} else if (isPlainObject(value)) {
Object.keys(value).forEach(addValue);
} else {
result.delete(b);
addValue(value);
}
});
break;
return [...result];
case 'var':
values.forEach(value => {
suggestions.add('$' + value);
});
break;
}
case TYPE_OBJECT:
// not sure what we need do here:
// - just filter keys from `a`
// - or filter key+value pairs?
// - take in account type of b? (array, Object.keys(b), scalar as a key)
return [...suggestions];
}
default:
return a - b;
}
},
mul: function(a, b) {
return a * b;
},
div: function(a, b) {
return a / b;
},
mod: function(a, b) {
return a % b;
},
eq: function(a, b) {
return a === b;
},
ne: function(a, b) {
return a !== b;
},
lt: function(a, b) {
return a < b;
},
lte: function(a, b) {
return a <= b;
},
gt: function(a, b) {
return a > b;
},
gte: function(a, b) {
return a >= b;
},
in: function(a, b) {
switch (this.type(b)) {
case TYPE_OBJECT:
return hasOwnProperty.call(b, a);
function findSourcePosPoints(source, pos, points, includeEmpty) {
const result = [];
default:
return b && typeof b.indexOf === 'function' ? b.indexOf(a) !== -1 : false;
}
},
regexp: function(data, rx) {
switch (this.type(data)) {
case TYPE_ARRAY:
return this.filter(data, current => rx.test(current));
for (let i = 0; i < points.length; i++) {
let [values, from, to, context] = points[i];
default:
return rx.test(data);
}
},
get: function(data, getter) {
const fn = typeof getter === 'function'
? getter
: current => getPropertyValue(current, getter);
if (pos >= from && pos <= to && (includeEmpty || values.size || values.length)) {
let current = source.substring(from, to);
switch (this.type(data)) {
case TYPE_ARRAY:
const result = new Set();
if (!/\S/.test(current)) {
current = '';
from = to = pos;
}
for (let i = 0; i < data.length; i++) {
addToSet(fn(data[i]), result);
}
return [...result];
default:
return data !== undefined ? fn(data) : data;
result.push({
context,
current,
from,
to,
values
});
}
},
recursive: function(data, getter) {
const result = new Set();
addToSet(this.get(data, getter), result);
result.forEach(current =>
addToSet(this.get(current, getter), result)
);
return [...result];
},
filter: function(data, query) {
switch (this.type(data)) {
case TYPE_ARRAY:
return data.filter(current =>
this.bool(query(current))
);
default:
return [];
}
}
});
var methods = Object.freeze({
bool: function(current) {
return buildin.bool(current);
},
keys: function(current) {
return Object.keys(current || {});
},
values: function(current) {
const values = new Set();
return result;
}
Object
.values(current || {})
.forEach(value => addToSet(value, values));
function compileFunction(source, statMode, tolerantMode, debug) {
function astToCode(node, scopeVars) {
if (Array.isArray(node)) {
const first = node[0];
let varName = false;
let i = 0;
return [...values];
},
entries: function(current) {
if (!current) {
return [];
}
if (first === '/*scope*/') {
// create new scope
scopeVars = scopeVars.slice();
i++;
} else if (typeof first === 'string' && first.startsWith('/*define:')) {
let [from, to] = first.substring(9, first.length - 2).split(',');
varName = source.substring(from, to);
return Object
.keys(current)
.map(key => ({ key, value: current[key] }));
},
pick: function(current, ref) {
if (!current) {
return undefined;
}
if (scopeVars.includes(`"${varName}"`)) {
throw new Error(`Identifier '$${varName}' has already been declared`);
}
if (typeof ref === 'function') {
if (Array.isArray(current)) {
return current.find(item => ref(item));
i++;
}
for (const key in current) {
if (hasOwnProperty.call(current, key)) {
if (ref(current[key])) {
return { key, value: current[key] };
}
}
for (; i < node.length; i++) {
astToCode(node[i], scopeVars);
}
return;
}
return Array.isArray(current) ? current[ref || 0] : current[ref];
},
mapToArray: function(current, keyProperty = 'key', valueProperty) {
const result = [];
for (let key in current) {
if (hasOwnProperty.call(current, key)) {
result.push(
valueProperty
? { [keyProperty]: key, [valueProperty]: current[key] }
: Object.assign({ [keyProperty]: key }, current[key])
);
if (varName) {
scopeVars.push(`"${varName}"`);
}
}
} else if (statMode && node.startsWith('/*')) {
if (node === '/*s*/') {
code.push('suggestPoint(');
} else if (node.startsWith('/*var:')) {
let [from, to] = node.substring(6, node.length - 2).split(',');
return result;
},
size: function(current) {
switch (buildin.type(current)) {
case TYPE_ARRAY:
return current.length;
if (from === to) {
while (to < source.length && isWhiteSpace(source, to)) {
to++;
}
}
case TYPE_OBJECT:
return Object.keys(current).length;
suggestPoints.push(`[[${scopeVars}], ${from}, ${to}, "var"]`);
} else {
const pointId = suggestSets.push('sp' + suggestSets.length + ' = new Set()') - 1;
const items = node.substring(2, node.length - 2).split(',');
default:
return (current && current.length) || 0;
}
},
sort: function(current, fn) {
if (buildin.type(current) !== TYPE_ARRAY) {
return current;
}
// FIXME: position correction should be in parser
for (let i = 0; i < items.length; i += 3) {
let from = Number(items[i]);
let to = Number(items[i + 1]);
let context = items[i + 2];
const frag = source.substring(from, to);
if (typeof fn === 'function') {
return current.slice().sort((a, b) => {
a = fn(a);
b = fn(b);
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return a.length < b.length ? -1 : 1;
}
for (let i = 0; i < a.length; i++) {
if (a[i] < b[i]) {
return -1;
} else if (a[i] > b[i]) {
return 1;
if (frag === '.[' || frag === '.(' || frag === '..(' ||
frag === '{' || frag === '[' || frag === '(' ||
from === to) {
from = to;
while (to < source.length && isWhiteSpace(source, to)) {
to++;
}
}
return 0;
suggestPoints.push(`[sp${pointId}, ${from}, ${to}, "${context || 'path'}"]`);
}
return a < b ? -1 : a > b;
});
code.push(`, sp${pointId})`);
}
} else {
code.push(node);
}
}
return current.slice().sort();
},
reverse: function(current) {
if (buildin.type(current) !== TYPE_ARRAY) {
return current;
}
const parser = tolerantMode ? tolerantParser : strictParser;
const code = [];
const suggestPoints = [];
let suggestSets = [];
let body = [];
let tree;
return current.slice().reverse();
},
group: function(current, keyGetter, valueGetter) {
if (typeof keyGetter !== 'function') {
keyGetter = noop;
}
if (debug) {
console.log('\n== compile ======');
console.log('source:', source);
}
if (typeof valueGetter !== 'function') {
valueGetter = self;
}
tree = parser.parse(source);
if (buildin.type(current) !== TYPE_ARRAY) {
current = [current];
}
// if (debug) {
// console.log('tree:', JSON.stringify(tree, null, 4));
// }
const map = new Map();
const result = [];
astToCode(tree, []);
current.forEach(item => {
const key = keyGetter(item);
if (map.has(key)) {
map.get(key).add(valueGetter(item));
} else {
map.set(key, new Set([valueGetter(item)]));
}
});
map.forEach((value, key) =>
result.push({ key, value: [...value] })
if (suggestSets.length) {
body.push(
'const ' + suggestSets.join(', ') + ';',
'const suggestPoint = (value, set) => set.add(value) && value;'
);
return result;
},
filter: function(current, fn) {
return buildin.filter(current, fn);
},
map: function(current, fn) {
return buildin.get(current, fn);
}
});
function compileFunction(expression, debug) {
var tree = parser.parse(expression);
var js = [];
body.push(
// preserved variables
'const $data = undefined, $context = undefined, $ctx = undefined, $array = undefined, $idx = undefined, $index = undefined;',
'let current = data;',
code.join('')
);
if (debug) {
console.log('\n==== compile ===');
console.log('expression:', expression);
console.log('tree:', tree);
if (statMode) {
body.push(`,[${suggestPoints}]`);
}
tree.forEach(function toJs(node) {
if (Array.isArray(node)) {
node.forEach(toJs);
} else {
js.push(node);
}
});
if (debug) {
console.log('js', js.join(''));
console.log('== body =========\n' + body.join('\n') + '\n=================\n');
}
return cache[expression] = new Function(
'fn', 'method', 'data', 'context', 'self',
[
'let current = data;',
'const $data = undefined, $context = undefined, $ctx = undefined, $array = undefined, $idx = undefined, $index = undefined;',
js.join('')
].join('\n')
);
return new Function('fn', 'method', 'data', 'context', 'self', body.join('\n'));
}
module.exports = function createQuery(expression, extraFunctions, debug) {
expression = String(expression).trim();
module.exports = function createQuery(source, options) {
options = options || {};
var localMethods = extraFunctions ? Object.assign({}, methods, extraFunctions) : methods;
var func = cache[expression] || compileFunction(expression, debug);
const debug = Boolean(options.debug);
const statMode = Boolean(options.stat);
const tolerantMode = Boolean(options.tolerant);
const localMethods = options.methods ? Object.assign({}, methods, options.methods) : methods;
const cache = statMode
? (tolerantMode ? cacheTollerantStat : cacheStrictStat)
: (tolerantMode ? cacheTollerant : cacheStrict);
let fn;
source = String(source);
if (cache.has(source)) {
fn = cache.get(source);
} else {
fn = compileFunction(source, statMode, tolerantMode, debug);
cache.set(source, fn);
}
if (debug) {
console.log('fn', func.toString());
console.log('fn', fn.toString());
}
if (statMode) {
return function query(data, context) {
const points = fn(buildin, localMethods, data, context, query);
return {
stat(pos, includeEmpty) {
const ranges = findSourcePosPoints(source, pos, points, includeEmpty);
ranges.forEach(entry => {
entry.values = [...entry.values];
});
return ranges.length ? ranges : null;
},
suggestion(pos, includeEmpty) {
const suggestions = [];
findSourcePosPoints(source, pos, points, includeEmpty).forEach(entry => {
const { context, current, from, to, values } = entry;
// console.log({current, variants:[...suggestions.get(range)], suggestions })
suggestions.push(
...valuesToSuggestions(context, values)
.map(value => ({
current,
type: contextToType[context],
value,
from,
to
}))
);
});
return suggestions.length ? suggestions : null;
}
};
};
}
return function query(data, context) {
return func(buildin, localMethods, data, context, query);
return fn(buildin, localMethods, data, context, query);
};
};
module.exports.version = require('../package.json').version;
module.exports.buildin = buildin;
module.exports.methods = methods;

@@ -1,64 +0,125 @@

const Jison = require('jison');
const { Parser } = require('jison');
function code(s) {
return '$$ = [' +
s[0].split(/(\$\d+)/g).map((x, i) => i % 2 ? x : JSON.stringify(x)) +
s[0].split(/(\$[\da-zA-Z_]+|\/\*(?:scope|define:\S+?|var:\S+?)\*\/|\/\*\S*@[\da-zA-Z_$]+(?:\/\S*@[\da-zA-Z_$]+)*\*\/\$?[\da-zA-Z_]+)/g).map(
(m, i) => {
if (i % 2 === 0 || m === '/*scope*/') {
return JSON.stringify(m);
}
if (m.startsWith('/*define:')) {
return '"/*define:" + ' + m.substring(9, m.length - 2) + '.range + "*/"';
}
if (m.startsWith('/*var:')) {
return '"/*var:" + ' + m.substring(6, m.length - 2) + '.range + "*/"';
}
if (m.startsWith('/*')) {
const content = m.substring(2, m.indexOf('*/'));
const expr = m.substr(content.length + 4);
const ranges = content.split('/').map(range => {
const [context, loc] = range.split('@');
return '" + @' + loc + '.range + ",' + context;
});
return (
'"/*s*/",' +
(expr[0] === '$' ? expr : '"' + expr + '"') +
',"/*' + ranges + '*/"'
);
}
return m;
}
).filter(term => term !== '""') +
'];';
}
var grammar = {
const switchToPreventRxState = 'if (this._input) this.begin("preventRx"); ';
const grammar = {
// Lexical tokens
lex: {
options: {
ranges: true
},
macros: {
wb: '\\b',
ows: '\\s*', // optional whitespaces
ws: '\\s+' // required whitespaces
ws: '\\s+', // required whitespaces
comment: '//.*?(\\r|\\n|$)+',
rx: '/(?:\\\\.|[^/])+/i?'
},
startConditions: {
preventRx: 0
},
rules: [
['\\({ows}', 'return "(";'],
['{ows}\\)', 'return ")";'],
['{ows}\\[{ows}', 'return "[";'],
['{ows}\\]', 'return "]";'],
['\\{{ows}', 'return "{";'],
['{ows}\\}', 'return "}";'],
// ignore comments and whitespaces
['{comment}', '/* a comment */'],
['{ws}', '/* a whitespace */'],
['{ows}={ows}', 'return "=";'],
['{ows}!={ows}', 'return "!=";'],
['{ows}~={ows}', 'return "~=";'],
['{ows}>={ows}', 'return ">=";'],
['{ows}<={ows}', 'return "<=";'],
['{ows}<{ows}', 'return "<";'],
['{ows}>{ows}', 'return ">";'],
['{ws}and{ws}', 'return "AND";'],
['{ws}or{ws}' , 'return "OR";'],
['{ws}in{ws}', 'return "IN";'],
['{ws}not{ws}in{ws}', 'return "NOTIN";'],
['not?{ws}', 'return "NOT";'],
// hack to prevent regexp consumption
[['preventRx'], '\\/', 'this.popState(); return "/";'],
// FIXME: using `this.done = false;` is hack, since `regexp-lexer` set done=true
// when no input left and doesn't take into account current state;
// should be fixed in `regexp-lexer`
[['preventRx'], '', 'this.done = false; this.popState();'],
['{wb}true{wb}', 'return "TRUE";'],
['{wb}false{wb}', 'return "FALSE";'],
['{wb}null{wb}', 'return "NULL";'],
['{wb}undefined{wb}', 'return "UNDEFINED";'],
// braces
['\\(', 'return "(";'],
['\\)', switchToPreventRxState + 'return ")";'],
['\\[', 'return "[";'],
['\\]', switchToPreventRxState + 'return "]";'],
['\\{', 'return "{";'],
['\\}', 'return "}";'],
// operators
['=', 'return "=";'],
['!=', 'return "!=";'],
['~=', 'return "~=";'],
['>=', 'return ">=";'],
['<=', 'return "<=";'],
['<', 'return "<";'],
['>', 'return ">";'],
['and{wb}', 'return "AND";'],
['or{wb}' , 'return "OR";'],
['in{wb}', 'return "IN";'],
['not{ws}in{wb}', 'return "NOTIN";'],
['not?{wb}', 'return "NOT";'],
// keywords
['true{wb}', 'return "TRUE";'],
['false{wb}', 'return "FALSE";'],
['null{wb}', 'return "NULL";'],
['undefined{wb}', 'return "UNDEFINED";'],
// self
['::self', 'return "SELF";'],
['[0-9]+(?:\\.[0-9]+)?\\b', 'return "NUMBER";'], // 212.321
['"(?:\\\\.|[^"])*"', 'return "STRING";'], // "foo" "with \" escaped"
["'(?:\\\\.|[^'])*'", 'return "STRING";'], // 'foo' 'with \' escaped'
['/(?:\\\\.|[^/])+/i?', 'return "REGEXP"'], // /foo/i
['[a-zA-Z_][a-zA-Z_$0-9]*', 'return "SYMBOL";'], // foo123
// comment
['{ows}//.*?(\\n|$)', '/* a comment */'],
// primitives
['\\d+(?:\\.\\d+)?{wb}', switchToPreventRxState + 'return "NUMBER";'], // 212.321
['"(?:\\\\.|[^"])*"', switchToPreventRxState + 'return "STRING";'], // "foo" "with \" escaped"
["'(?:\\\\.|[^'])*'", switchToPreventRxState + 'return "STRING";'], // 'foo' 'with \' escaped'
['{rx}', 'return "REGEXP"'], // /foo/i
['[a-zA-Z_][a-zA-Z_$0-9]*', switchToPreventRxState + 'return "SYMBOL";'], // foo123
['{ows}\\.{1,3}', 'return yytext.trim();'],
['{ows}\\?{ows}', 'return "?";'],
['{ows},{ows}', 'return ",";'],
['{ows}:{ows}', 'return ":";'],
['{ows};{ows}', 'return ";";'],
['{ows}\\-{ows}', 'return "-";'],
['{ows}\\+{ows}', 'return "+";'],
['{ows}\\*{ows}', 'return "*";'],
['{ows}\\/{ows}', 'return "/";'],
['{ows}\\%{ows}', 'return "%";'],
// ['{ows}\\^{ows}', 'return "%";'],
// operators
['\\.\\.\\(', 'return "..(";'],
['\\.\\(', 'return ".(";'],
['\\.\\[', 'return ".[";'],
['\\.\\.\\.', 'return "...";'],
['\\.\\.', 'return "..";'],
['\\.', 'return ".";'],
['\\?', 'return "?";'],
[',', 'return ",";'],
[':', 'return ":";'],
[';', 'return ";";'],
['\\-', 'return "-";'],
['\\+', 'return "+";'],
['\\*', 'return "*";'],
['\\/', 'return "/";'],
['\\%', 'return "%";'],
// special vars
['@', 'return "@";'],

@@ -85,4 +146,4 @@ ['#', 'return "#";'],

['left', '*', '/', '%'],
// ['left', '^'],
['left', '.', '..', '...']
['left', '.', '..', '...'],
['left', '.(', '.[', '..(']
],

@@ -97,6 +158,10 @@ // Grammar

block: [
['nonEmptyBlock', code`/*scope*/$1`],
['definitions', code`/*scope*/$1\nreturn current`],
['', code`/*scope*/return /*@$*/current`]
],
nonEmptyBlock: [
['definitions e', code`$1\nreturn $2`],
['definitions', code`$1\nreturn current`],
['e', code`return $1`],
['', code`return current`]
['e', code`return $1`]
],

@@ -110,4 +175,4 @@

def: [
['$ SYMBOL ;', code`const $$2 = fn.get(current, "$2");`],
['$ SYMBOL : e ;', code`const $$2 = $4;`]
['$ SYMBOL ;', code`/*define:@2*/const $$2 = fn.get(/*key@2*/current, "$2");`],
['$ SYMBOL : e ;', code`/*define:@2*/const $$2 = $4;`]
],

@@ -124,5 +189,8 @@

['function', code`$1`],
['op', code`$1`]
],
op: [
['NOT e', code`!fn.bool($2)`],
['e IN e', code`fn.in($1, $3)`],
['e IN e', code`fn.in($1, /*in-value@1*/$3)`],
['e NOTIN e', code`!fn.in($1, $3)`],

@@ -137,4 +205,4 @@ ['e AND e', code`fn.bool($1) ? $3 : $1`],

['e % e', code`fn.mod($1, $3)`],
['e = e', code`fn.eq($1, $3)`],
['e != e', code`fn.ne($1, $3)`],
['e = e', code`fn.eq(/*value@3*/$1, $3)`],
['e != e', code`fn.ne(/*value@3*/$1, $3)`],
['e < e', code`fn.lt($1, $3)`],

@@ -162,4 +230,4 @@ ['e <= e', code`fn.lte($1, $3)`],

['#', code`context`],
['$', code`current`],
['$ SYMBOL', code`$$2`],
['$', code`/*var:@1*/current`],
['$ SYMBOL', code`/*var:@$*/typeof $$2 !== 'undefined' ? $$2 : undefined`],
['STRING', code`$1`],

@@ -170,20 +238,23 @@ ['NUMBER', code`$1`],

['array', code`$1`],
['SYMBOL', code`fn.get(current, "$1")`],
['. SYMBOL', code`fn.get(current, "$2")`],
['SYMBOL', code`/*var:@1*/fn.get(/*@1*/current, "$1", 'xxx')`],
['. SYMBOL', code`fn.get(/*@2*/current, "$2")`],
['( e )', code`($2)`],
['. ( block )', code`fn.get(current, current => { $3 })`],
['SYMBOL ( arguments )', code`method.$1(current$3)`],
['. SYMBOL ( arguments )', code`method.$2(current$4)`],
['.. SYMBOL', code`fn.recursive(current, "$2")`],
['.. ( block )', code`fn.recursive(current, current => { $3 })`],
['. [ block ]', code`fn.filter(current, current => { $3 })`]
['.( block )', code`fn.get(current, current => { $2 })`],
['SYMBOL ( )', code`method.$1(/*@1/@2*/current)`],
['SYMBOL ( arguments )', code`method.$1(/*@1*/current, $3)`],
['. SYMBOL ( )', code`method.$2(/*@2/@3*/current)`],
['. SYMBOL ( arguments )', code`method.$2(/*@2*/current, $4)`],
['.. SYMBOL', code`fn.recursive(/*@2*/current, "$2")`],
['..( block )', code`fn.recursive(current, current => { $2 })`],
['.[ block ]', code`fn.filter(current, current => { $2 })`]
],
relativePath: [
['query . SYMBOL', code`fn.get($1, "$3")`],
['query . SYMBOL ( arguments )', code`method.$3($1$5)`],
['query . ( block )', code`fn.get($1, current => { $4 })`],
['query .. SYMBOL', code`fn.recursive($1, "$3")`],
['query .. ( block )', code`fn.recursive($1, current => { $4 })`],
['query . [ block ]', code`fn.filter($1, current => { $4 })`],
['query . SYMBOL', code`fn.get(/*@3*/$1, "$3")`],
['query . SYMBOL ( )', code`method.$3(/*@3/@4*/$1)`],
['query . SYMBOL ( arguments )', code`method.$3(/*@3*/$1, $5)`],
['query .( block )', code`fn.get($1, current => { $3 })`],
['query .. SYMBOL', code`fn.recursive(/*@3*/$1, "$3")`],
['query ..( block )', code`fn.recursive($1, current => { $3 })`],
['query .[ block ]', code`fn.filter($1, current => { $3 })`],
['query [ e ]', code`fn.get($1, $3)`]

@@ -193,13 +264,8 @@ ],

arguments: [
['', code``],
['argumentList', code`$1`]
['e', code`$1`],
['arguments , e', code`$1, $3`]
],
argumentList: [
['e', code`, $1`],
['argumentList , e', code`$1, $3`]
],
object: [
['{ }', code`({})`],
['{ }', code`(/*@1*/current, {})`],
['{ properties }', code`({ $2 })`]

@@ -214,4 +280,4 @@ ],

property: [
['SYMBOL', code`$1: fn.get(current, "$1")`],
['$ SYMBOL', code`$2: $$2`],
['SYMBOL', code`/*var:@1*/$1: fn.get(/*@1*/current, "$1")`],
['$ SYMBOL', code`/*var:@$*/$2: typeof $$2 !== 'undefined' ? $$2 : undefined`],
['SYMBOL : e', code`$1: $3`],

@@ -224,5 +290,4 @@ ['[ e ] : e', code`[$2]: $5`],

array: [
['[ ]', code`[]`],
['[ e ]', code`[$2]`],
['[ e , arrayItems ]', code`[$2, $4]`]
['[ ]', code`(/*@1*/current, [])`],
['[ arrayItems ]', code`[$2]`]
],

@@ -241,2 +306,90 @@

module.exports = new Jison.Parser(grammar);
const tolerantScopeStart = new Set([
':', ';',
'\\.', '\\.\\.', ',',
'\\+', '\\-', '\\*', '\\/', '\\%',
'=', '!=', '~=', '>=', '<=', /* '<', '>', */
'and{wb}', 'or{wb}', 'in{wb}', 'not{ws}in{wb}', 'not?{wb}'
]);
const tolerantGrammar = Object.assign({}, grammar, {
lex: Object.assign({}, grammar.lex, {
startConditions: Object.assign({}, grammar.lex.startConditions, {
suggestPoint: 1,
suggestPointWhenWhitespace: 1,
suggestPointVar: 1,
suggestPointVarDisabled: 0
}),
rules: [
[['suggestPoint'],
// prevent suggestions before rx
'(?=({ows}{comment})*{ows}{rx})',
'this.popState();'
],
[['suggestPoint'],
'(?=({ows}{comment})*{ows}([\\]\\)\\}\\<\\>\\+\\-\\*\\/,~!=:;]|$))',
// FIXME: using `this.done = false;` is hack, since `regexp-lexer` set done=true
// when no input left and doesn't take into account current state;
// should be fixed in `regexp-lexer`
'this.popState(); this.done = false; yytext = "_"; ' + switchToPreventRxState + 'return "SYMBOL";'
],
[['suggestPointWhenWhitespace'],
'{ws}',
'this.popState(); this.begin("suggestPoint");'
],
[['suggestPointVar'],
'(?=({ows}{comment})*{ows}([:;]|$))',
// FIXME: using `this.done = false;` is hack, since `regexp-lexer` set done=true
// when no input left and doesn't take into account current state;
// should be fixed in `regexp-lexer`
'this.popState(); this.done = false; yytext = "_"; ' + switchToPreventRxState + 'return "SYMBOL";'
],
[['suggestPointVarDisabled'],
'(?=;)',
'this.popState();'
],
[['suggestPoint', 'suggestPointWhenWhitespace', 'suggestPointVar'],
'',
'this.popState();'
]
].concat(
grammar.lex.rules.map(entry => {
let [sc, rule, action] = entry.length === 3 ? entry : [undefined, ...entry];
if (rule === '\\$') {
action = `if (this.conditionStack.indexOf("suggestPointVarDisabled") === -1) this.begin("suggestPointVar"); ${action}`;
} else if (tolerantScopeStart.has(rule)) {
action = `this.begin("suggestPoint${
rule.endsWith('{wb}') ? 'WhenWhitespace' : ''
}"); ${action}`;
}
if (rule === ':') {
action = 'this.begin("suggestPointVarDisabled"); ' + action;
}
return entry.length === 3 ? [sc, rule, action] : [rule, action];
})
)
})
});
// guard to keep in sync tolerantScopeStart and lex rules
tolerantScopeStart.forEach(rule => {
if (!tolerantGrammar.lex.rules.some(lexRule => lexRule[0] === rule)) {
throw new Error('Rule missed in lexer: "' + rule.replace(/\\/g, '\\\\') + '"');
}
});
const strictParser = new Parser(grammar);
const tolerantParser = new Parser(tolerantGrammar);
// tolerantParser.lexer.setInput('$;');
// while (!tolerantParser.lexer.done) {
// console.log(tolerantParser.lexer.conditionStack);
// console.log('>>', tolerantParser.lexer.next());
// }
// process.exit();
module.exports = strictParser;
module.exports.strict = strictParser;
module.exports.tolerant = tolerantParser;

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc