Skip to content

Commit

Permalink
feat(parser): new parser
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip committed Apr 1, 2024
1 parent 93ecef9 commit e4bfa34
Show file tree
Hide file tree
Showing 15 changed files with 625 additions and 148 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ coverage/
*.log
cjs/
.gen/
src/parser/parser.mjs
3 changes: 1 addition & 2 deletions .nycrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"src/**/*.mjs"
],
"exclude": [
"**/__*__/**",
"src/parser/parser.mjs"
"**/__*__/**"
],
"reporter": [
"lcovonly",
Expand Down
1 change: 0 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import globals from 'globals';
export default [
{
files: ['**/*.mjs', '**/*.js'],
ignores: ['src/parser/parser.mjs'],
languageOptions: {
ecmaVersion: 2023,
parser: babelParser,
Expand Down
35 changes: 0 additions & 35 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nimma",
"version": "0.4.2",
"version": "0.5.0",
"description": "Scalable JSONPath engine.",
"keywords": [
"json",
Expand Down Expand Up @@ -28,8 +28,8 @@
"require": "./cjs/index.cjs"
},
"./parser": {
"import": "./src/parser/parser.mjs",
"require": "./cjs/parser/parser.cjs"
"import": "./src/parser/index.mjs",
"require": "./cjs/parser/index.cjs"
},
"./parser/jsep": {
"import": "./src/parser/jsep.mjs",
Expand All @@ -53,7 +53,6 @@
"url": "https://github.com/P0lip/nimma"
},
"scripts": {
"prebuild": "peggy --format es -o src/parser/parser.mjs src/parser/parser.peg",
"build": "rollup -c",
"lint": "ls-lint && eslint --cache --cache-location .cache/ src && prettier --log-level error --ignore-path .gitignore --check --cache --cache-location .cache/.prettier src",
"test": "c8 mocha --config .mocharc ./src/**/__tests__/**/*.test.mjs && karma start karma.conf.cjs --log-level=error",
Expand Down Expand Up @@ -86,7 +85,6 @@
"lodash-es": "^4.17.21",
"mocha": "^10.2.0",
"mocha-each": "^2.0.1",
"peggy": "^3.0.2",
"prettier": "^3.2.5",
"rollup": "^4.9.6"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ describe('parseFilterExpression', () => {

it('throws upon unknown shorthand', () => {
expect(print.bind(null, `?(@foo())`)).to.throw(
SyntaxError,
`Unsupported shorthand '@foo'`,
`Unsupported shorthand "@foo"`,
);
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/codegen/baseline/generators.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ function processAtIdentifier(tree, name) {
);
}

throw new SyntaxError(`Unsupported shorthand '${name}'`);
throw Error(`Unsupported shorthand "${name}"`);
}
}

Expand Down
6 changes: 2 additions & 4 deletions src/core/__tests__/index.test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable no-undef */
import { expect } from 'chai';

import { ParserError } from '../../runtime/errors/index.mjs';
import Nimma from '../index.mjs';

describe('Core', () => {
Expand All @@ -13,10 +12,9 @@ describe('Core', () => {
try {
fn();
} catch (e) {
expect(e.errors[0]).to.be.instanceof(ParserError);
expect(e.errors[0].cause.name).to.eq('SyntaxError');
expect(e.errors[0]).to.be.instanceof(SyntaxError);
expect(e.errors[0].message).to.eq(
'Expected "^", "~", or end of input but "." found.',
'Expected "^", "~", or end of input but "." found at 4',
);
}
});
Expand Down
122 changes: 106 additions & 16 deletions src/parser/__tests__/parser.test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import forEach from 'mocha-each';

import { parse } from '../parser.mjs';
import parse from '../index.mjs';

describe('Parser', () => {
it('goessner samples', () => {
Expand Down Expand Up @@ -200,6 +200,37 @@ describe('Parser', () => {
});

it('filter expressions', () => {
expect(parse('$[(@.length-1)]')).to.deep.equal([
{
type: 'SliceExpression',
value: [-1, Infinity, 1],
deep: false,
},
]);
expect(parse('$[( @.length - 2 )]')).to.deep.equal([
{
type: 'SliceExpression',
value: [-2, Infinity, 1],
deep: false,
},
]);
expect(parse('$[( @[ "length" ] - 10 )]')).to.deep.equal([
{
type: 'SliceExpression',
value: [-10, Infinity, 1],
deep: false,
},
]);
expect(parse('$[( @["length"] - 5 )]')).to.deep.equal([
{
type: 'SliceExpression',
value: [-5, Infinity, 1],
deep: false,
},
]);
});

it('script filter expressions', () => {
expect(parse('$[?(@property === "@.schema")]')).to.deep.equal([
{
type: 'ScriptFilterExpression',
Expand Down Expand Up @@ -346,6 +377,36 @@ describe('Parser', () => {
},
);

it('skips whitespaces', () => {
expect(parse('$.[ name ] [?( @.abc )]\t ..@@test( )')).to.deep.equal([
{
type: 'MemberExpression',
value: 'name',
deep: true,
},
{
type: 'ScriptFilterExpression',
value: ' @.abc ',
deep: false,
},
{
type: 'ScriptFilterExpression',
value: '@@test( )',
deep: true,
},
]);
});

it.skip('handles escapable', () => {
expect(parse(`$["'name\\"'","test\\\\",'"a']`)).to.deep.equal([
{
type: 'MultipleMemberExpression',
value: ['name"', 'test\\'],
deep: false,
},
]);
});

describe('invalid expressions', () => {
it('empty expression or does not start with $', () => {
expect(() => parse('')).to.throw('Expected "$" but end of input found.');
Expand All @@ -355,56 +416,85 @@ describe('Parser', () => {

it('invalid member expression', () => {
expect(() => parse('$info')).to.throw(
'Expected ".", "..", "^", "~", or end of input but "i" found.',
'Expected ".", "..", "^", "~", or end of input but "i" found at 1.',
);
expect(() => parse('$.')).to.throw(
'Expected "*", "@", "[", [$_\\-], [0-9], or [A-Za-z] but end of input found.',
'Expected valid name but end of input found at 2.',
);
});

it('key expression used in the wrong place', () => {
expect(() => parse('$.name~.a')).to.throw(
'Expected "^", "~", or end of input but "." found.',
'Expected "^", "~", or end of input but "." found at 7.',
);
});

it('unclosed quotes', () => {
expect(() => parse('$.name["a]')).to.throw(
`Expected "\\"" or [^"] but end of input found.`,
`Expected """ but end of input found at 10.`,
);
expect(() => parse('$.name["\']')).to.throw(
`Expected "\\"" or [^"] but end of input found.`,
`Expected """ but end of input found at 10.`,
);
});

it('invalid step in slice expressions', () => {
expect(() => parse('$.name[::test]')).to.throw(
'Expected "-" or [0-9] but "t" found.',
'Expected "-" or [0-9] but "t" found at 9.',
);
expect(() => parse('$.name[::-]')).to.throw(
'Expected [0-9] but "]" found at 10.',
);
});

it('invalid shorthands', () => {
expect(() => parse('$..@@()')).to.throw('Expected [a-z] but "(" found.');
expect(() => parse('$..@@()')).to.throw(
'Expected [a-z] but "(" found at 5.',
);
expect(() => parse('$..@@test)')).to.throw(
'Expected "()" or [a-z] but ")" found.',
'Expected "(" but ")" found at 9.',
);
expect(() => parse('$..@@test(')).to.throw(
'Expected "()" or [a-z] but "(" found.',
);
expect(() => parse('$..@@test)')).to.throw(
'Expected "()" or [a-z] but ")" found.',
'Expected ")" but end of input found at 10.',
);
expect(() => parse('$..@')).to.throw(
'Expected "@" or [a-z] but end of input found.',
'Expected [a-z] but end of input found at 4.',
);
});

it('invalid filter expressions', () => {
expect(() => parse('$[(')).to.throw(
'Expected "@" but end of input found at 3.',
);
expect(() => parse('$[(@')).to.throw(
'Expected "." or "[" but end of input found at 4.',
);
expect(() => parse('$[(@.len - 1)]')).to.throw(
'Expected "length" but "len - " found at 11.',
);
expect(() => parse('$[(@length - 1)]')).to.throw(
'Expected "." or "[" but "l" found at 4.',
);
expect(() => parse('$[(@[length]-2)]')).to.throw(
`Expected """ or "'" at 5.`,
);
expect(() => parse('$[(@.length + 1))')).to.throw(
'Expected "-" but "+" found at 12.',
);
expect(() => parse('$[(@.length - -5))')).to.throw(
'Expected positive number but "-5" found at 14.',
);
expect(() => parse('$[(@.length - 0))')).to.throw(
'Expected positive number but "0" found at 14.',
);
});

it('unclosed brackets', () => {
expect(() => parse('$.name[0')).to.throw(
'Expected "\'", ",", ":", "\\"", "]", [$_\\-], [0-9], or [A-Za-z] but end of input found.',
'Expected "]" but end of input found at 8.',
);
expect(() => parse('$.store["[name]"')).to.throw(
'Expected "\'", ",", "\\"", "]", [$_\\-], [0-9], or [A-Za-z] but end of input found.',
'Expected "]" but end of input found at 16.',
);
});
});
Expand Down
13 changes: 1 addition & 12 deletions src/parser/index.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1 @@
import { ParserError } from '../runtime/errors/index.mjs';
import * as parser from './parser.mjs';

const { parse } = parser;

export default function (input) {
try {
return parse(input);
} catch (e) {
throw new ParserError(e.message, input, { cause: e });
}
}
export { parser as default } from './parser.mjs';
Loading

0 comments on commit e4bfa34

Please sign in to comment.