Skip to content

Commit

Permalink
Reorganise types and types bundling
Browse files Browse the repository at this point in the history
  • Loading branch information
Glen Van Ginkel committed Jul 27, 2020
1 parent 2b865c4 commit 8be0487
Show file tree
Hide file tree
Showing 14 changed files with 303 additions and 285 deletions.
7 changes: 6 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ module.exports = {
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
project: `./tsconfig.json`
project: `./tsconfig.eslint.json`
},
rules: {
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off",
},
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@metrichor/jmespath",
"description": "Typescript implementation of the JMESPath spec (100% compliant)",
"version": "0.1.4",
"version": "0.1.5",
"author": {
"name": "Oxford Nanopore Technologies",
"email": "[email protected]",
Expand Down
75 changes: 71 additions & 4 deletions src/Lexer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,70 @@
import { Token, LexerToken, JSONValue } from './typings';
import { isAlpha, isNum, isAlphaNum, trimLeft } from './utils';
import { JSONValue } from '.';

export enum Token {
TOK_EOF = 'EOF',
TOK_UNQUOTEDIDENTIFIER = 'UnquotedIdentifier',
TOK_QUOTEDIDENTIFIER = 'QuotedIdentifier',
TOK_RBRACKET = 'Rbracket',
TOK_RPAREN = 'Rparen',
TOK_COMMA = 'Comma',
TOK_COLON = 'Colon',
TOK_RBRACE = 'Rbrace',
TOK_NUMBER = 'Number',
TOK_CURRENT = 'Current',
TOK_EXPREF = 'Expref',
TOK_PIPE = 'Pipe',
TOK_OR = 'Or',
TOK_AND = 'And',
TOK_EQ = 'EQ',
TOK_GT = 'GT',
TOK_LT = 'LT',
TOK_GTE = 'GTE',
TOK_LTE = 'LTE',
TOK_NE = 'NE',
TOK_FLATTEN = 'Flatten',
TOK_STAR = 'Star',
TOK_FILTER = 'Filter',
TOK_DOT = 'Dot',
TOK_NOT = 'Not',
TOK_LBRACE = 'Lbrace',
TOK_LBRACKET = 'Lbracket',
TOK_LPAREN = 'Lparen',
TOK_LITERAL = 'Literal',
}

export type LexerTokenValue = string | number | JSONValue;

export interface LexerToken {
type: Token;
value: LexerTokenValue;
start: number;
}

export interface Node {
type: string;
}

export interface ValueNode<T = LexerTokenValue> extends Node {
value: T;
}

export interface FieldNode extends Node {
name: LexerTokenValue;
}

export interface KeyValuePairNode extends FieldNode, ValueNode<ExpressionNodeTree> {}

export interface ExpressionNode<T = ExpressionNodeTree> extends Node {
children: T[];
jmespathType?: Token;
}

export interface ComparitorNode extends ExpressionNode {
name: Token;
}

export type ExpressionNodeTree = Node | ExpressionNode | FieldNode | ValueNode;

export const basicTokens = {
'(': Token.TOK_LPAREN,
Expand Down Expand Up @@ -49,7 +114,7 @@ class StreamLexer {
} else if (basicTokens[stream[this._current]] !== undefined) {
tokens.push({
start: this._current,
type: basicTokens[stream[this._current]],
type: basicTokens[stream[this._current]] as Token,
value: stream[this._current],
});
this._current += 1;
Expand Down Expand Up @@ -138,7 +203,7 @@ class StreamLexer {
this._current = current;
}
this._current += 1;
return JSON.parse(stream.slice(start, this._current));
return JSON.parse(stream.slice(start, this._current)) as string;
}

private consumeRawStringLiteral(stream: string): string {
Expand Down Expand Up @@ -231,7 +296,9 @@ class StreamLexer {
}
let literalString = trimLeft(stream.slice(start, this._current));
literalString = literalString.replace('\\`', '`');
const literal = this.looksLikeJSON(literalString) ? JSON.parse(literalString) : JSON.parse(`"${literalString}"`);
const literal: JSONValue = this.looksLikeJSON(literalString)
? (JSON.parse(literalString) as JSONValue)
: (JSON.parse(`"${literalString}"`) as string);
this._current += 1;
return literal;
}
Expand Down
14 changes: 7 additions & 7 deletions src/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ValueNode,
Node,
Token,
} from './typings';
} from './Lexer';
import Lexer from './Lexer';

const bindingPower: { [token: string]: number } = {
Expand Down Expand Up @@ -50,7 +50,7 @@ class TokenParser {
const ast = this.expression(0);
if (this.lookahead(0) !== Token.TOK_EOF) {
const token = this.lookaheadToken(0);
this.errorToken(token, `Unexpected token type: ${token.type}, value: ${token.value}`);
this.errorToken(token, `Unexpected token type: ${token.type}, value: ${token.value as string}`);
}
return ast;
}
Expand Down Expand Up @@ -236,12 +236,12 @@ class TokenParser {
return;
} else {
const token = this.lookaheadToken(0);
this.errorToken(token, `Expected ${tokenType}, got: ${token.type}`);
this.errorToken(token, `Expected ${tokenType as string}, got: ${token.type}`);
}
}

private errorToken(token: LexerToken, message = ''): never {
const error = new Error(message || `Invalid token (${token.type}): "${token.value}"`);
const error = new Error(message || `Invalid token (${token.type}): "${token.value as string}"`);
error.name = 'ParserError';
throw error;
}
Expand Down Expand Up @@ -283,7 +283,7 @@ class TokenParser {
this.advance();
} else {
const token = this.lookaheadToken(0);
this.errorToken(token, `Syntax error, unexpected token: ${token.value}(${token.type})`);
this.errorToken(token, `Syntax error, unexpected token: ${token.value as string}(${token.type})`);
}
currentTokenType = this.lookahead(0);
}
Expand Down Expand Up @@ -314,7 +314,7 @@ class TokenParser {
return this.parseMultiselectHash();
}
const token = this.lookaheadToken(0);
this.errorToken(token, `Syntax error, unexpected token: ${token.value}(${token.type})`);
this.errorToken(token, `Syntax error, unexpected token: ${token.value as string}(${token.type})`);
}

private parseProjectionRHS(rbp: number): ExpressionNodeTree {
Expand All @@ -332,7 +332,7 @@ class TokenParser {
return this.parseDotRHS(rbp);
}
const token = this.lookaheadToken(0);
this.errorToken(token, `Syntax error, unexpected token: ${token.value}(${token.type})`);
this.errorToken(token, `Syntax error, unexpected token: ${token.value as string}(${token.type})`);
}

private parseMultiselectList(): ExpressionNode {
Expand Down
53 changes: 36 additions & 17 deletions src/Runtime.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
import { TreeInterpreter } from './TreeInterpreter';
import {
Token,
JSONValue,
JSONObject,
FunctionTable,
InputArgument,
InputSignature,
RuntimeFunction,
JSONArray,
ExpressionNode,
} from './typings';
import { Token, ExpressionNode } from './Lexer';

import { isObject } from './utils';
import { JSONValue, JSONObject, JSONArray, ObjectDict } from '.';

export enum InputArgument {
TYPE_NUMBER = 0,
TYPE_ANY = 1,
TYPE_STRING = 2,
TYPE_ARRAY = 3,
TYPE_OBJECT = 4,
TYPE_BOOLEAN = 5,
TYPE_EXPREF = 6,
TYPE_NULL = 7,
TYPE_ARRAY_NUMBER = 8,
TYPE_ARRAY_STRING = 9,
}

export interface InputSignature {
types: InputArgument[];
variadic?: boolean;
}

export type RuntimeFunction<T, U> = (resolvedArgs: T) => U;

export interface FunctionSignature {
_func: RuntimeFunction<any, any>;
_signature: InputSignature[];
}

export interface FunctionTable {
[functionName: string]: FunctionSignature;
}

export class Runtime {
_interpreter?: TreeInterpreter;
Expand Down Expand Up @@ -144,7 +165,7 @@ export class Runtime {
case '[object Null]':
return InputArgument.TYPE_NULL;
case '[object Object]':
if ((obj as JSONObject).jmespathType === Token.TOK_EXPREF) {
if ((obj as ObjectDict).jmespathType === Token.TOK_EXPREF) {
return InputArgument.TYPE_EXPREF;
}
return InputArgument.TYPE_OBJECT;
Expand Down Expand Up @@ -213,10 +234,10 @@ export class Runtime {
};

private functionLength: RuntimeFunction<[string | JSONArray | JSONObject], number> = ([inputValue]) => {
if (!isObject(inputValue)) {
return (inputValue as string | JSONArray).length;
if (!isObject(inputValue as JSONValue)) {
return inputValue.length as number;
}
return Object.keys(inputValue as JSONObject).length;
return Object.keys(inputValue).length;
};

private functionMap = (resolvedArgs: any[]) => {
Expand Down Expand Up @@ -446,7 +467,6 @@ export class Runtime {
return Object.values(inputObject);
};

/* eslint-disable @typescript-eslint/camelcase */
private functionTable: FunctionTable = {
abs: {
_func: this.functionAbs,
Expand Down Expand Up @@ -683,5 +703,4 @@ export class Runtime {
],
},
};
/* eslint-enable @typescript-eslint/camelcase */
}
26 changes: 13 additions & 13 deletions src/TreeInterpreter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {
Token,
JSONValue,
ExpressionNodeTree,
FieldNode,
ExpressionNode,
ValueNode,
ComparitorNode,
KeyValuePairNode,
} from './typings';
import { isFalse, objValues, isObject, strictDeepEqual } from './utils';
} from './Lexer';
import { isFalse, isObject, strictDeepEqual } from './utils';
import { Runtime } from './Runtime';
import { JSONValue, JSONObject } from '.';

export class TreeInterpreter {
runtime: Runtime;
Expand Down Expand Up @@ -39,8 +39,8 @@ export class TreeInterpreter {
if (value === null) {
return null;
}
if (isObject(value)) {
field = value[(node as FieldNode).name as string];
if (isObject(value as JSONValue)) {
field = (value as JSONObject)[(node as FieldNode).name as string];
if (field === undefined) {
return null;
}
Expand Down Expand Up @@ -106,11 +106,11 @@ export class TreeInterpreter {
return collected as JSONValue;
case 'ValueProjection':
base = this.visit((node as ExpressionNode).children[0], value);
if (!isObject(base)) {
if (!isObject(base as JSONValue)) {
return null;
}
collected = [];
const values = objValues(base);
const values = Object.values(base as JSONObject);
for (i = 0; i < values.length; i += 1) {
current = this.visit((node as ExpressionNode).children[1], values[i]);
if (current !== null) {
Expand All @@ -127,7 +127,7 @@ export class TreeInterpreter {
const finalResults = [];
for (i = 0; i < base.length; i += 1) {
matched = this.visit((node as ExpressionNode).children[2], base[i]);
if (!isFalse(matched)) {
if (!isFalse(matched as JSONValue)) {
filtered.push(base[i]);
}
}
Expand All @@ -143,10 +143,10 @@ export class TreeInterpreter {
second = this.visit((node as ExpressionNode).children[1], value);
switch ((node as ComparitorNode).name) {
case Token.TOK_EQ:
result = strictDeepEqual(first, second);
result = strictDeepEqual(first as JSONValue, second as JSONValue);
break;
case Token.TOK_NE:
result = !strictDeepEqual(first, second);
result = !strictDeepEqual(first as JSONValue, second as JSONValue);
break;
case Token.TOK_GT:
result = (first as number) > (second as number);
Expand Down Expand Up @@ -203,20 +203,20 @@ export class TreeInterpreter {
return collected;
case 'OrExpression':
matched = this.visit((node as ExpressionNode).children[0], value);
if (isFalse(matched)) {
if (isFalse(matched as JSONValue)) {
matched = this.visit((node as ExpressionNode).children[1], value);
}
return matched;
case 'AndExpression':
first = this.visit((node as ExpressionNode).children[0], value);

if (isFalse(first)) {
if (isFalse(first as JSONValue)) {
return first;
}
return this.visit((node as ExpressionNode).children[1], value);
case 'NotExpression':
first = this.visit((node as ExpressionNode).children[0], value);
return isFalse(first);
return isFalse(first as JSONValue);
case 'Literal':
return (node as ValueNode<JSONValue>).value;
case Token.TOK_PIPE:
Expand Down
18 changes: 9 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import Parser from './Parser';
import Lexer from './Lexer';
import TreeInterpreterInst from './TreeInterpreter';
import {
ExpressionNodeTree,
LexerToken,
JSONValue,
InputArgument,
InputSignature,
RuntimeFunction,
} from './typings/index';
import { ExpressionNodeTree, LexerToken } from './Lexer';
import { InputArgument, RuntimeFunction, InputSignature } from './Runtime';

export type { FunctionSignature, RuntimeFunction, InputSignature } from './typings/index';
export type { FunctionSignature, RuntimeFunction, InputSignature } from './Runtime';
export type ObjectDict<T = unknown> = Record<string, T | undefined>;

export type JSONPrimitive = string | number | boolean | null;
export type JSONValue = JSONPrimitive | JSONObject | JSONArray;
export type JSONObject = { [member: string]: JSONValue };
export type JSONArray = JSONValue[];

export const TYPE_ANY = InputArgument.TYPE_ANY;
export const TYPE_ARRAY = InputArgument.TYPE_ARRAY;
Expand Down
Loading

0 comments on commit 8be0487

Please sign in to comment.