npm run format -ws

This commit is contained in:
Josh Gross
2023-01-09 19:02:19 -05:00
parent 3df70b2491
commit c49981ec64
113 changed files with 3252 additions and 4573 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
import { ExpressionData } from "./data";
import { Token } from "./lexer";
import {ExpressionData} from "./data";
import {Token} from "./lexer";
export interface ExprVisitor<R> {
visitLiteral(literal: Literal): R;
+26 -47
View File
@@ -1,9 +1,9 @@
import { complete, CompletionItem, trimTokenVector } from "./completion";
import { BooleanData } from "./data/boolean";
import { Dictionary } from "./data/dictionary";
import { StringData } from "./data/string";
import { FunctionInfo } from "./funcs/info";
import { Lexer, TokenType } from "./lexer";
import {complete, CompletionItem, trimTokenVector} from "./completion";
import {BooleanData} from "./data/boolean";
import {Dictionary} from "./data/dictionary";
import {StringData} from "./data/string";
import {FunctionInfo} from "./funcs/info";
import {Lexer, TokenType} from "./lexer";
const testContext = new Dictionary(
{
@@ -11,24 +11,24 @@ const testContext = new Dictionary(
value: new Dictionary(
{
key: "FOO",
value: new StringData(""),
value: new StringData("")
},
{
key: "BAR_TEST",
value: new StringData(""),
value: new StringData("")
}
),
)
},
{
key: "secrets",
value: new Dictionary({
key: "AWS_TOKEN",
value: new BooleanData(true),
}),
value: new BooleanData(true)
})
},
{
key: "hashFiles(1,255)",
value: new Dictionary(),
value: new Dictionary()
}
);
@@ -38,17 +38,13 @@ const testComplete = (input: string): CompletionItem[] => {
const pos = input.indexOf("|");
input = input.replace("|", "");
const results = complete(
input.slice(0, pos >= 0 ? pos : input.length),
testContext,
testFunctions
);
const results = complete(input.slice(0, pos >= 0 ? pos : input.length), testContext, testFunctions);
return results;
};
function completionItems(...labels: string[]): CompletionItem[] {
return labels.map((label) => ({ label, function: false }));
return labels.map(label => ({label, function: false}));
}
describe("auto-complete", () => {
@@ -58,7 +54,7 @@ describe("auto-complete", () => {
label: "toJson",
description:
"`toJSON(value)`\n\nReturns a pretty-print JSON representation of `value`. You can use this function to debug the information provided in contexts.",
function: true,
function: true
};
expect(testComplete("to")).toContainEqual(expected);
expect(testComplete("toJs")).toContainEqual(expected);
@@ -69,7 +65,7 @@ describe("auto-complete", () => {
it("removes parentheses from passed in function context", () => {
expect(testComplete("|")).toContainEqual({
label: "hashFiles",
function: true,
function: true
});
});
});
@@ -92,9 +88,7 @@ describe("auto-complete", () => {
});
it("provides suggestions for contexts in function call", () => {
expect(testComplete("toJSON(env.|)")).toEqual(
completionItems("BAR_TEST", "FOO")
);
expect(testComplete("toJSON(env.|)")).toEqual(completionItems("BAR_TEST", "FOO"));
});
});
});
@@ -106,34 +100,19 @@ describe("trimTokenVector", () => {
}>([
{
input: "env.",
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.EOF],
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.EOF]
},
{
input: "github.act",
expected: [
TokenType.IDENTIFIER,
TokenType.DOT,
TokenType.IDENTIFIER,
TokenType.EOF,
],
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF]
},
{
input: "1 == github.act",
expected: [
TokenType.IDENTIFIER,
TokenType.DOT,
TokenType.IDENTIFIER,
TokenType.EOF,
],
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF]
},
{
input: "github.mona == github.act",
expected: [
TokenType.IDENTIFIER,
TokenType.DOT,
TokenType.IDENTIFIER,
TokenType.EOF,
],
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF]
},
{
input: "github['test'].",
@@ -143,14 +122,14 @@ describe("trimTokenVector", () => {
TokenType.STRING,
TokenType.RIGHT_BRACKET,
TokenType.DOT,
TokenType.EOF,
],
},
])("$input", ({ input, expected }) => {
TokenType.EOF
]
}
])("$input", ({input, expected}) => {
const l = new Lexer(input);
const lr = l.lex();
const tv = trimTokenVector(lr.tokens);
expect(tv.map((x) => x.type)).toEqual(expected);
expect(tv.map(x => x.type)).toEqual(expected);
});
});
+13 -20
View File
@@ -1,10 +1,10 @@
import { Dictionary, isDictionary } from "./data/dictionary";
import { ExpressionData } from "./data/expressiondata";
import { Evaluator } from "./evaluator";
import { wellKnownFunctions } from "./funcs";
import { FunctionInfo } from "./funcs/info";
import { Lexer, Token, TokenType } from "./lexer";
import { Parser } from "./parser";
import {Dictionary, isDictionary} from "./data/dictionary";
import {ExpressionData} from "./data/expressiondata";
import {Evaluator} from "./evaluator";
import {wellKnownFunctions} from "./funcs";
import {FunctionInfo} from "./funcs/info";
import {Lexer, Token, TokenType} from "./lexer";
import {Parser} from "./parser";
export type CompletionItem = {
label: string;
@@ -20,11 +20,7 @@ export type CompletionItem = {
// - context.path.| or context.path['| -- auto-complete context access
// - toJS| -- auto-complete function call or top-level
// - | -- auto-complete function call or top-level context access
export function complete(
input: string,
context: Dictionary,
extensionFunctions: FunctionInfo[]
): CompletionItem[] {
export function complete(input: string, context: Dictionary, extensionFunctions: FunctionInfo[]): CompletionItem[] {
// Lex
const lexer = new Lexer(input);
const lexResult = lexer.lex();
@@ -68,7 +64,7 @@ export function complete(
const p = new Parser(
pathTokenVector,
context.pairs().map((x) => x.key),
context.pairs().map(x => x.key),
extensionFunctions
);
const expr = p.parse();
@@ -82,14 +78,11 @@ export function complete(
function functionItems(extensionFunctions: FunctionInfo[]): CompletionItem[] {
const result: CompletionItem[] = [];
for (const fdef of [
...Object.values(wellKnownFunctions),
...extensionFunctions,
]) {
for (const fdef of [...Object.values(wellKnownFunctions), ...extensionFunctions]) {
result.push({
label: fdef.name,
description: fdef.description,
function: true,
function: true
});
}
@@ -104,7 +97,7 @@ function contextKeys(context: ExpressionData): CompletionItem[] {
return (
context
.pairs()
.map((x) => completionItemFromContext(x.key))
.map(x => completionItemFromContext(x.key))
// Sort contexts
.sort((a, b) => a.label.localeCompare(b.label))
);
@@ -119,7 +112,7 @@ function completionItemFromContext(context: string): CompletionItem {
return {
label: isFunc ? context.substring(0, parenIndex) : context,
function: isFunc,
function: isFunc
};
}
+1 -6
View File
@@ -1,9 +1,4 @@
import {
ExpressionData,
ExpressionDataInterface,
Kind,
kindStr,
} from "./expressiondata";
import {ExpressionData, ExpressionDataInterface, Kind, kindStr} from "./expressiondata";
export class Array implements ExpressionDataInterface {
private v: ExpressionData[] = [];
+1 -1
View File
@@ -1,4 +1,4 @@
import { ExpressionDataInterface, Kind } from "./expressiondata";
import {ExpressionDataInterface, Kind} from "./expressiondata";
export class BooleanData implements ExpressionDataInterface {
constructor(public readonly value: boolean) {}
@@ -1,12 +1,12 @@
import { Dictionary } from "./dictionary";
import { StringData } from "./string";
import {Dictionary} from "./dictionary";
import {StringData} from "./string";
describe("dictionary", () => {
it("pairs contains all values", () => {
const d = new Dictionary();
d.add("ABC", new StringData("val"));
expect(d.pairs()).toEqual([{ key: "ABC", value: new StringData("val") }]);
expect(d.pairs()).toEqual([{key: "ABC", value: new StringData("val")}]);
});
it("does not add duplicate entries", () => {
@@ -14,6 +14,6 @@ describe("dictionary", () => {
d.add("ABC", new StringData("val1"));
d.add("abc", new StringData("val2"));
expect(d.pairs()).toEqual([{ key: "ABC", value: new StringData("val1") }]);
expect(d.pairs()).toEqual([{key: "ABC", value: new StringData("val1")}]);
});
});
+3 -9
View File
@@ -1,15 +1,9 @@
import {
ExpressionData,
ExpressionDataInterface,
Kind,
kindStr,
Pair,
} from "./expressiondata";
import {ExpressionData, ExpressionDataInterface, Kind, kindStr, Pair} from "./expressiondata";
export class Dictionary implements ExpressionDataInterface {
private keys: string[] = [];
private v: ExpressionData[] = [];
private indexMap: { [key: string]: number } = {};
private indexMap: {[key: string]: number} = {};
constructor(...pairs: Pair[]) {
for (const p of pairs) {
@@ -56,7 +50,7 @@ export class Dictionary implements ExpressionDataInterface {
const result: Pair[] = [];
for (const key of this.keys) {
result.push({ key, value: this.v[this.indexMap[key.toLowerCase()]] });
result.push({key, value: this.v[this.indexMap[key.toLowerCase()]]});
}
return result;
+8 -14
View File
@@ -1,9 +1,9 @@
import { Dictionary } from "./dictionary";
import { Null } from "./null";
import { Array } from "./array";
import { StringData } from "./string";
import { NumberData } from "./number";
import { BooleanData } from "./boolean";
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {Array} from "./array";
import {StringData} from "./string";
import {NumberData} from "./number";
import {BooleanData} from "./boolean";
export enum Kind {
String = 0,
@@ -12,7 +12,7 @@ export enum Kind {
Boolean,
Number,
CaseSensitiveDictionary,
Null,
Null
}
export function kindStr(k: Kind): string {
@@ -43,13 +43,7 @@ export interface ExpressionDataInterface {
number(): number;
}
export type ExpressionData =
| Array
| Dictionary
| StringData
| BooleanData
| NumberData
| Null;
export type ExpressionData = Array | Dictionary | StringData | BooleanData | NumberData | Null;
export type Pair = {
key: string;
+9 -9
View File
@@ -1,9 +1,9 @@
export { Array } from "./array";
export { BooleanData } from "./boolean";
export { Dictionary } from "./dictionary";
export { ExpressionData, Kind } from "./expressiondata";
export { Null } from "./null";
export { NumberData } from "./number";
export { replacer } from "./replacer";
export { reviver } from "./reviver";
export { StringData } from "./string";
export {Array} from "./array";
export {BooleanData} from "./boolean";
export {Dictionary} from "./dictionary";
export {ExpressionData, Kind} from "./expressiondata";
export {Null} from "./null";
export {NumberData} from "./number";
export {replacer} from "./replacer";
export {reviver} from "./reviver";
export {StringData} from "./string";
+1 -5
View File
@@ -1,8 +1,4 @@
import {
ExpressionData,
ExpressionDataInterface,
Kind,
} from "./expressiondata";
import {ExpressionData, ExpressionDataInterface, Kind} from "./expressiondata";
export class Null implements ExpressionDataInterface {
constructor() {}
+1 -1
View File
@@ -1,4 +1,4 @@
import { ExpressionDataInterface, Kind } from "./expressiondata";
import {ExpressionDataInterface, Kind} from "./expressiondata";
export class NumberData implements ExpressionDataInterface {
constructor(public readonly value: number) {}
+12 -20
View File
@@ -1,9 +1,9 @@
import { Array } from "./array";
import { Dictionary } from "./dictionary";
import { Null } from "./null";
import { NumberData } from "./number";
import { replacer } from "./replacer";
import { StringData } from "./string";
import {Array} from "./array";
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {NumberData} from "./number";
import {replacer} from "./replacer";
import {StringData} from "./string";
describe("replacer", () => {
it("null", () => {
@@ -11,22 +11,14 @@ describe("replacer", () => {
});
it("array", () => {
expect(
JSON.stringify(
new Array(new StringData("a"), new StringData("b")),
replacer,
" "
)
).toEqual('[\n "a",\n "b"\n]');
expect(JSON.stringify(new Array(new StringData("a"), new StringData("b")), replacer, " ")).toEqual(
'[\n "a",\n "b"\n]'
);
});
it("dictionary", () => {
expect(
JSON.stringify(
new Dictionary({ key: "a", value: new NumberData(42) }),
replacer,
" "
)
).toEqual('{\n "a": 42\n}');
expect(JSON.stringify(new Dictionary({key: "a", value: new NumberData(42)}), replacer, " ")).toEqual(
'{\n "a": 42\n}'
);
});
});
+6 -6
View File
@@ -1,9 +1,9 @@
import { Array } from "./array";
import { BooleanData } from "./boolean";
import { Dictionary } from "./dictionary";
import { Null } from "./null";
import { NumberData } from "./number";
import { StringData } from "./string";
import {Array} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {NumberData} from "./number";
import {StringData} from "./string";
/**
* Replacer can be passed to JSON.stringify to convert an ExpressionData object into plain JSON
+25 -28
View File
@@ -1,11 +1,11 @@
import { Array } from "./array";
import { BooleanData } from "./boolean";
import { Dictionary } from "./dictionary";
import { ExpressionData } from "./expressiondata";
import { Null } from "./null";
import { NumberData } from "./number";
import { reviver } from "./reviver";
import { StringData } from "./string";
import {Array} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {ExpressionData} from "./expressiondata";
import {Null} from "./null";
import {NumberData} from "./number";
import {reviver} from "./reviver";
import {StringData} from "./string";
describe("reviver", () => {
const tests: {
@@ -16,32 +16,32 @@ describe("reviver", () => {
{
name: "null",
data: "null",
want: new Null(),
want: new Null()
},
{
name: "number",
data: "1",
want: new NumberData(1),
want: new NumberData(1)
},
{
name: "string",
data: `"a"`,
want: new StringData("a"),
want: new StringData("a")
},
{
name: "true boolean",
data: "true",
want: new BooleanData(true),
want: new BooleanData(true)
},
{
name: "false boolean",
data: "false",
want: new BooleanData(false),
want: new BooleanData(false)
},
{
name: "array",
data: `[1,2,3]`,
want: new Array(new NumberData(1), new NumberData(2), new NumberData(3)),
want: new Array(new NumberData(1), new NumberData(2), new NumberData(3))
},
{
name: "nested array",
@@ -51,7 +51,7 @@ describe("reviver", () => {
new NumberData(2),
new Array(new NumberData(3), new NumberData(4)),
new NumberData(5)
),
)
},
{
name: "complex array",
@@ -59,26 +59,23 @@ describe("reviver", () => {
want: new Array(
new Dictionary({
key: "a",
value: new Array(new BooleanData(true), new NumberData(2)),
value: new Array(new BooleanData(true), new NumberData(2))
}),
new Dictionary({ key: "b", value: new StringData("three") }),
new Dictionary({ key: "c", value: new Null() })
),
new Dictionary({key: "b", value: new StringData("three")}),
new Dictionary({key: "c", value: new Null()})
)
},
{
name: "dictionary",
data: `{ "object1": {} }`,
want: new Dictionary({
key: "object1",
value: new Dictionary(),
}),
},
value: new Dictionary()
})
}
];
test.each(tests)(
"$name",
({ data, want }: { data: string; want: ExpressionData }) => {
expect(JSON.parse(data, reviver)).toEqual(want);
}
);
test.each(tests)("$name", ({data, want}: {data: string; want: ExpressionData}) => {
expect(JSON.parse(data, reviver)).toEqual(want);
});
});
+9 -9
View File
@@ -1,10 +1,10 @@
import { Array as dArray } from "./array";
import { BooleanData } from "./boolean";
import { Dictionary } from "./dictionary";
import { ExpressionData, Pair } from "./expressiondata";
import { Null } from "./null";
import { NumberData } from "./number";
import { StringData } from "./string";
import {Array as dArray} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {ExpressionData, Pair} from "./expressiondata";
import {Null} from "./null";
import {NumberData} from "./number";
import {StringData} from "./string";
/**
* Reviver can be passed to `JSON.parse` to convert plain JSON into an `ExpressionData` object.
@@ -35,10 +35,10 @@ export function reviver(key: string, val: any): ExpressionData {
if (typeof val === "object") {
return new Dictionary(
...Object.keys(val).map(
(k) =>
k =>
({
key: k,
value: val[k],
value: val[k]
} as Pair)
)
);
+1 -1
View File
@@ -1,4 +1,4 @@
import { ExpressionDataInterface, Kind } from "./expressiondata";
import {ExpressionDataInterface, Kind} from "./expressiondata";
export class StringData implements ExpressionDataInterface {
constructor(public readonly value: string) {}
+4 -4
View File
@@ -1,4 +1,4 @@
import { Pos, Token, tokenString } from "./lexer";
import {Pos, Token, tokenString} from "./lexer";
export const MAX_PARSER_DEPTH = 50;
export const MAX_EXPRESSION_LENGTH = 21000;
@@ -13,7 +13,7 @@ export enum ErrorType {
ErrorTooFewParameters,
ErrorTooManyParameters,
ErrorUnrecognizedContext,
ErrorUnrecognizedFunction,
ErrorUnrecognizedFunction
}
export class ExpressionError extends Error {
@@ -42,8 +42,8 @@ function errorDescription(typ: ErrorType): string {
return "Too few parameters supplied";
case ErrorType.ErrorTooManyParameters:
return "Too many parameters supplied";
case ErrorType.ErrorUnrecognizedContext:
return "Unrecognized named-value";
case ErrorType.ErrorUnrecognizedContext:
return "Unrecognized named-value";
case ErrorType.ErrorUnrecognizedFunction:
return "Unrecognized function";
default: // Should never reach here.
+4 -4
View File
@@ -1,7 +1,7 @@
import * as data from "./data";
import { Evaluator } from "./evaluator";
import { Lexer } from "./lexer";
import { Parser } from "./parser";
import {Evaluator} from "./evaluator";
import {Lexer} from "./lexer";
import {Parser} from "./parser";
test("evaluator", () => {
const input = "foo['']";
@@ -18,7 +18,7 @@ test("evaluator", () => {
expr,
new data.Dictionary({
key: "foo",
value: new data.Dictionary({ key: "bar", value: new data.NumberData(42) }),
value: new data.Dictionary({key: "bar", value: new data.NumberData(42)})
})
);
const eresult = evaluator.evaluate();
+11 -22
View File
@@ -9,14 +9,14 @@ import {
Literal,
Logical,
Star,
Unary,
Unary
} from "./ast";
import * as data from "./data";
import { FilteredArray } from "./filtered_array";
import { wellKnownFunctions } from "./funcs";
import { idxHelper } from "./idxHelper";
import { TokenType } from "./lexer";
import { equals, falsy, greaterThan, lessThan, truthy } from "./result";
import {FilteredArray} from "./filtered_array";
import {wellKnownFunctions} from "./funcs";
import {idxHelper} from "./idxHelper";
import {TokenType} from "./lexer";
import {equals, falsy, greaterThan, lessThan, truthy} from "./result";
export class EvaluationError extends Error {}
@@ -60,9 +60,7 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
return new data.BooleanData(greaterThan(left, right));
case TokenType.GREATER_EQUAL:
return new data.BooleanData(
equals(left, right) || greaterThan(left, right)
);
return new data.BooleanData(equals(left, right) || greaterThan(left, right));
case TokenType.LESS:
return new data.BooleanData(lessThan(left, right));
@@ -150,25 +148,19 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
visitFunctionCall(functionCall: FunctionCall): data.ExpressionData {
// Evaluate arguments
const args = functionCall.args.map((arg) => this.eval(arg));
const args = functionCall.args.map(arg => this.eval(arg));
return fcall(functionCall, args);
}
}
function fcall(
fc: FunctionCall,
args: data.ExpressionData[]
): data.ExpressionData {
function fcall(fc: FunctionCall, args: data.ExpressionData[]): data.ExpressionData {
const f = wellKnownFunctions[fc.functionName.lexeme.toLowerCase()];
return f.call(...args);
}
function filteredArrayAccess(
fa: FilteredArray,
idx: idxHelper
): data.ExpressionData {
function filteredArrayAccess(fa: FilteredArray, idx: idxHelper): data.ExpressionData {
const result = new FilteredArray();
for (const item of fa.values()) {
@@ -225,10 +217,7 @@ function arrayAccess(a: data.Array, idx: idxHelper): data.ExpressionData {
return new data.Null();
}
function objectAccess(
obj: data.Dictionary,
idx: idxHelper
): data.ExpressionData {
function objectAccess(obj: data.Dictionary, idx: idxHelper): data.ExpressionData {
if (idx.star) {
const fa = new FilteredArray(...obj.values());
return fa;
+14 -21
View File
@@ -1,13 +1,13 @@
import { ErrorType, ExpressionError } from "./errors";
import { contains } from "./funcs/contains";
import { endswith } from "./funcs/endswith";
import { format } from "./funcs/format";
import { fromjson } from "./funcs/fromjson";
import { FunctionDefinition, FunctionInfo } from "./funcs/info";
import { join } from "./funcs/join";
import { startswith } from "./funcs/startswith";
import { tojson } from "./funcs/tojson";
import { Token } from "./lexer";
import {ErrorType, ExpressionError} from "./errors";
import {contains} from "./funcs/contains";
import {endswith} from "./funcs/endswith";
import {format} from "./funcs/format";
import {fromjson} from "./funcs/fromjson";
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
import {join} from "./funcs/join";
import {startswith} from "./funcs/startswith";
import {tojson} from "./funcs/tojson";
import {Token} from "./lexer";
export type ParseContext = {
allowUnknownKeywords: boolean;
@@ -15,24 +15,20 @@ export type ParseContext = {
extensionFunctions: Map<string, FunctionInfo>;
};
export const wellKnownFunctions: { [name: string]: FunctionDefinition } = {
export const wellKnownFunctions: {[name: string]: FunctionDefinition} = {
contains: contains,
endswith: endswith,
format: format,
fromjson: fromjson,
join: join,
startswith: startswith,
tojson: tojson,
tojson: tojson
};
// validateFunction returns the function definition for the given function name.
// If the function does not exist or an incorrect number of arguments is provided,
// an error is returned.
export function validateFunction(
context: ParseContext,
identifier: Token,
argCount: number
) {
export function validateFunction(context: ParseContext, identifier: Token, argCount: number) {
// Expression function names are case-insensitive.
const name = identifier.lexeme.toLowerCase();
@@ -42,10 +38,7 @@ export function validateFunction(
f = context.extensionFunctions.get(name);
if (!f) {
if (!context.allowUnknownKeywords) {
throw new ExpressionError(
ErrorType.ErrorUnrecognizedFunction,
identifier
);
throw new ExpressionError(ErrorType.ErrorUnrecognizedFunction, identifier);
}
// Skip argument validation for unknown functions
+4 -4
View File
@@ -1,6 +1,6 @@
import { Array, BooleanData, ExpressionData, Kind } from "../data";
import { equals } from "../result";
import { FunctionDefinition } from "./info";
import {Array, BooleanData, ExpressionData, Kind} from "../data";
import {equals} from "../result";
import {FunctionDefinition} from "./info";
export const contains: FunctionDefinition = {
name: "contains",
@@ -32,5 +32,5 @@ export const contains: FunctionDefinition = {
}
return new BooleanData(false);
},
}
};
+4 -4
View File
@@ -1,6 +1,6 @@
import { BooleanData, ExpressionData } from "../data";
import { toUpperSpecial } from "../result";
import { FunctionDefinition } from "./info";
import {BooleanData, ExpressionData} from "../data";
import {toUpperSpecial} from "../result";
import {FunctionDefinition} from "./info";
export const endswith: FunctionDefinition = {
name: "endsWith",
@@ -23,5 +23,5 @@ export const endswith: FunctionDefinition = {
const rs = toUpperSpecial(right.coerceString());
return new BooleanData(ls.endsWith(rs));
},
}
};
+3 -5
View File
@@ -1,13 +1,11 @@
import { Null, NumberData, StringData } from "../data";
import { format } from "./format";
import {Null, NumberData, StringData} from "../data";
import {format} from "./format";
describe("format", () => {
it("null", () => {
expect(format.call(new StringData("{0}"), new Null())).toEqual(new StringData(""));
});
it("number", () => {
expect(format.call(new StringData("{0}"), new NumberData(42))).toEqual(
new StringData("42")
);
expect(format.call(new StringData("{0}"), new NumberData(42))).toEqual(new StringData("42"));
});
});
+6 -8
View File
@@ -1,5 +1,5 @@
import { ExpressionData, StringData } from "../data";
import { FunctionDefinition } from "./info";
import {ExpressionData, StringData} from "../data";
import {FunctionDefinition} from "./info";
export const format: FunctionDefinition = {
name: "format",
@@ -32,9 +32,7 @@ export const format: FunctionDefinition = {
if (argIndex.success) {
// Check parameter count
if (1 + argIndex.result > args.length - 1) {
throw new Error(
`The following format string references more arguments than were supplied: ${fs}`
);
throw new Error(`The following format string references more arguments than were supplied: ${fs}`);
}
// Append the portion before the left brace
@@ -69,7 +67,7 @@ export const format: FunctionDefinition = {
}
return new StringData(result.join(""));
},
}
};
function safeCharAt(string: string, index: number): string {
@@ -95,7 +93,7 @@ function readArgIndex(string: string, startIndex: number): ArgIndex {
// Validate at least one digit
if (length < 1) {
return <ArgIndex>{
success: false,
success: false
};
}
@@ -105,7 +103,7 @@ function readArgIndex(string: string, startIndex: number): ArgIndex {
return <ArgIndex>{
success: !isNaN(result),
result: result,
endIndex: endIndex,
endIndex: endIndex
};
}
+4 -4
View File
@@ -1,6 +1,6 @@
import { ExpressionData } from "../data";
import { reviver } from "../data/reviver";
import { FunctionDefinition } from "./info";
import {ExpressionData} from "../data";
import {reviver} from "../data/reviver";
import {FunctionDefinition} from "./info";
export const fromjson: FunctionDefinition = {
name: "fromJson",
@@ -18,5 +18,5 @@ export const fromjson: FunctionDefinition = {
const d = JSON.parse(is, reviver);
return d;
},
}
};
+1 -1
View File
@@ -1,4 +1,4 @@
import { ExpressionData } from "../data";
import {ExpressionData} from "../data";
export interface FunctionInfo {
name: string;
+4 -4
View File
@@ -1,5 +1,5 @@
import { Array, ExpressionData, Kind, StringData } from "../data";
import { FunctionDefinition } from "./info";
import {Array, ExpressionData, Kind, StringData} from "../data";
import {FunctionDefinition} from "./info";
export const join: FunctionDefinition = {
name: "join",
@@ -25,11 +25,11 @@ export const join: FunctionDefinition = {
return new StringData(
(args[0] as Array)
.values()
.map((item) => item.coerceString())
.map(item => item.coerceString())
.join(separator)
);
}
return new StringData("");
},
}
};
+4 -4
View File
@@ -1,6 +1,6 @@
import { BooleanData, ExpressionData } from "../data";
import { toUpperSpecial } from "../result";
import { FunctionDefinition } from "./info";
import {BooleanData, ExpressionData} from "../data";
import {toUpperSpecial} from "../result";
import {FunctionDefinition} from "./info";
export const startswith: FunctionDefinition = {
name: "startsWith",
@@ -23,5 +23,5 @@ export const startswith: FunctionDefinition = {
const rs = toUpperSpecial(right.coerceString());
return new BooleanData(ls.startsWith(rs));
},
}
};
+4 -4
View File
@@ -1,6 +1,6 @@
import { ExpressionData, StringData } from "../data";
import { replacer } from "../data/replacer";
import { FunctionDefinition } from "./info";
import {ExpressionData, StringData} from "../data";
import {replacer} from "../data/replacer";
import {FunctionDefinition} from "./info";
export const tojson: FunctionDefinition = {
name: "toJson",
@@ -10,5 +10,5 @@ export const tojson: FunctionDefinition = {
maxArgs: 1,
call: (...args: ExpressionData[]): ExpressionData => {
return new StringData(JSON.stringify(args[0], replacer, " "));
},
}
};
+1 -1
View File
@@ -1,4 +1,4 @@
import { ExpressionData } from "./data";
import {ExpressionData} from "./data";
export class idxHelper {
public readonly str: string | undefined;
+5 -5
View File
@@ -1,6 +1,6 @@
export { complete } from "./completion";
export {complete} from "./completion";
export * as data from "./data";
export { ExpressionError } from "./errors";
export { Evaluator } from "./evaluator";
export { Lexer } from "./lexer";
export { Parser } from "./parser";
export {ExpressionError} from "./errors";
export {Evaluator} from "./evaluator";
export {Lexer} from "./lexer";
export {Parser} from "./parser";
+41 -58
View File
@@ -1,4 +1,4 @@
import { Lexer, Token, TokenType } from "./lexer";
import {Lexer, Token, TokenType} from "./lexer";
describe("lexer", () => {
const tests: {
@@ -6,26 +6,26 @@ describe("lexer", () => {
tokenType: TokenType[];
token?: Token;
}[] = [
{ input: "<", tokenType: [TokenType.LESS] },
{ input: ">", tokenType: [TokenType.GREATER] },
{input: "<", tokenType: [TokenType.LESS]},
{input: ">", tokenType: [TokenType.GREATER]},
{ input: "!=", tokenType: [TokenType.BANG_EQUAL] },
{ input: "==", tokenType: [TokenType.EQUAL_EQUAL] },
{ input: "<=", tokenType: [TokenType.LESS_EQUAL] },
{ input: ">=", tokenType: [TokenType.GREATER_EQUAL] },
{input: "!=", tokenType: [TokenType.BANG_EQUAL]},
{input: "==", tokenType: [TokenType.EQUAL_EQUAL]},
{input: "<=", tokenType: [TokenType.LESS_EQUAL]},
{input: ">=", tokenType: [TokenType.GREATER_EQUAL]},
{ input: "&&", tokenType: [TokenType.AND] },
{ input: "||", tokenType: [TokenType.OR] },
{input: "&&", tokenType: [TokenType.AND]},
{input: "||", tokenType: [TokenType.OR]},
// Numbers
{ input: "12", tokenType: [TokenType.NUMBER] },
{ input: "12.0", tokenType: [TokenType.NUMBER] },
{ input: "0", tokenType: [TokenType.NUMBER] },
{ input: "-0", tokenType: [TokenType.NUMBER] },
{ input: "-12.0", tokenType: [TokenType.NUMBER] },
{input: "12", tokenType: [TokenType.NUMBER]},
{input: "12.0", tokenType: [TokenType.NUMBER]},
{input: "0", tokenType: [TokenType.NUMBER]},
{input: "-0", tokenType: [TokenType.NUMBER]},
{input: "-12.0", tokenType: [TokenType.NUMBER]},
// Strings
{ input: "'It''s okay'", tokenType: [TokenType.STRING] },
{input: "'It''s okay'", tokenType: [TokenType.STRING]},
{
input: "format('{0} == ''queued''', needs)",
tokenType: [
@@ -34,34 +34,28 @@ describe("lexer", () => {
TokenType.STRING,
TokenType.COMMA,
TokenType.IDENTIFIER,
TokenType.RIGHT_PAREN,
],
TokenType.RIGHT_PAREN
]
},
// Arrays
{
input: "[1,2]",
tokenType: [
TokenType.LEFT_BRACKET,
TokenType.NUMBER,
TokenType.COMMA,
TokenType.NUMBER,
TokenType.RIGHT_BRACKET,
],
tokenType: [TokenType.LEFT_BRACKET, TokenType.NUMBER, TokenType.COMMA, TokenType.NUMBER, TokenType.RIGHT_BRACKET]
},
// Simple expressions
{
input: "1 == 2",
tokenType: [TokenType.NUMBER, TokenType.EQUAL_EQUAL, TokenType.NUMBER],
tokenType: [TokenType.NUMBER, TokenType.EQUAL_EQUAL, TokenType.NUMBER]
},
{
input: "1== 1",
tokenType: [TokenType.NUMBER, TokenType.EQUAL_EQUAL, TokenType.NUMBER],
tokenType: [TokenType.NUMBER, TokenType.EQUAL_EQUAL, TokenType.NUMBER]
},
{
input: "1< 1",
tokenType: [TokenType.NUMBER, TokenType.LESS, TokenType.NUMBER],
tokenType: [TokenType.NUMBER, TokenType.LESS, TokenType.NUMBER]
},
// Identifiers
@@ -73,9 +67,9 @@ describe("lexer", () => {
lexeme: "github",
pos: {
line: 0,
column: 0,
},
},
column: 0
}
}
},
// Keywords
@@ -87,9 +81,9 @@ describe("lexer", () => {
lexeme: "true",
pos: {
line: 0,
column: 0,
},
},
column: 0
}
}
},
{
@@ -100,9 +94,9 @@ describe("lexer", () => {
lexeme: "false",
pos: {
line: 0,
column: 0,
},
},
column: 0
}
}
},
{
@@ -113,32 +107,21 @@ describe("lexer", () => {
lexeme: "null",
pos: {
line: 0,
column: 0,
},
},
},
column: 0
}
}
}
];
test.each(tests)(
"$input",
({
input,
tokenType,
token,
}: {
input: string;
tokenType: TokenType[];
token?: Token;
}) => {
const l = new Lexer(input);
test.each(tests)("$input", ({input, tokenType, token}: {input: string; tokenType: TokenType[]; token?: Token}) => {
const l = new Lexer(input);
const r = l.lex();
const r = l.lex();
const want = r.tokens.map((t) => t.type);
const want = r.tokens.map(t => t.type);
tokenType.push(TokenType.EOF);
tokenType.push(TokenType.EOF);
expect(want).toEqual(tokenType);
}
);
expect(want).toEqual(tokenType);
});
});
+17 -40
View File
@@ -1,5 +1,5 @@
import { StringData } from "./data";
import { MAX_EXPRESSION_LENGTH } from "./errors";
import {StringData} from "./data";
import {MAX_EXPRESSION_LENGTH} from "./errors";
export enum TokenType {
UNKNOWN,
@@ -28,7 +28,7 @@ export enum TokenType {
FALSE,
NULL,
EOF,
EOF
}
export type Pos = {
@@ -104,7 +104,6 @@ export class Lexer {
this.previous() != TokenType.RIGHT_BRACKET &&
this.previous() != TokenType.RIGHT_PAREN &&
this.previous() != TokenType.STAR
) {
this.consumeNumber();
} else {
@@ -118,9 +117,7 @@ export class Lexer {
break;
case "!":
this.addToken(
this.match("=") ? TokenType.BANG_EQUAL : TokenType.BANG
);
this.addToken(this.match("=") ? TokenType.BANG_EQUAL : TokenType.BANG);
break;
case "=":
@@ -134,15 +131,11 @@ export class Lexer {
break;
case "<":
this.addToken(
this.match("=") ? TokenType.LESS_EQUAL : TokenType.LESS
);
this.addToken(this.match("=") ? TokenType.LESS_EQUAL : TokenType.LESS);
break;
case ">":
this.addToken(
this.match("=") ? TokenType.GREATER_EQUAL : TokenType.GREATER
);
this.addToken(this.match("=") ? TokenType.GREATER_EQUAL : TokenType.GREATER);
break;
case "&":
@@ -199,18 +192,18 @@ export class Lexer {
this.tokens.push({
type: TokenType.EOF,
lexeme: "",
pos: this.pos(),
pos: this.pos()
});
return {
tokens: this.tokens,
tokens: this.tokens
};
}
private pos(): Pos {
return {
line: this.line,
column: this.start - this.lastLineOffset,
column: this.start - this.lastLineOffset
};
}
@@ -268,7 +261,7 @@ export class Lexer {
type,
lexeme: this.input.substring(this.start, this.offset),
pos: this.pos(),
value,
value
});
}
@@ -282,9 +275,7 @@ export class Lexer {
if (isNaN(value)) {
throw new Error(
`Unexpected symbol: '${lexeme}'. Located at position ${
this.start + 1
} within expression: ${this.input}`
`Unexpected symbol: '${lexeme}'. Located at position ${this.start + 1} within expression: ${this.input}`
);
}
@@ -304,11 +295,9 @@ export class Lexer {
if (this.atEnd()) {
// Unterminated string
throw new Error(
`Unexpected symbol: '${this.input.substring(
this.start
)}'. Located at position ${this.start + 1} within expression: ${
this.input
}`
`Unexpected symbol: '${this.input.substring(this.start)}'. Located at position ${
this.start + 1
} within expression: ${this.input}`
);
}
@@ -360,9 +349,7 @@ export class Lexer {
if (!isLegalIdentifier(lexeme)) {
throw new Error(
`Unexpected symbol: '${lexeme}'. Located at position ${
this.start + 1
} within expression: ${this.input}`
`Unexpected symbol: '${lexeme}'. Located at position ${this.start + 1} within expression: ${this.input}`
);
}
@@ -400,19 +387,9 @@ function isLegalIdentifier(str: string): boolean {
}
const first = str[0];
if (
(first >= "a" && first <= "z") ||
(first >= "A" && first <= "Z") ||
first == "_"
) {
if ((first >= "a" && first <= "z") || (first >= "A" && first <= "Z") || first == "_") {
for (const c of str.substring(1).split("")) {
if (
(c >= "a" && c <= "z") ||
(c >= "A" && c <= "Z") ||
(c >= "0" && c <= "9") ||
c == "_" ||
c == "-"
) {
if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || (c >= "0" && c <= "9") || c == "_" || c == "-") {
// OK
} else {
return false;
+17 -58
View File
@@ -1,20 +1,9 @@
import {
Binary,
ContextAccess,
Expr,
FunctionCall,
Grouping,
IndexAccess,
Literal,
Logical,
Star,
Unary,
} from "./ast";
import {Binary, ContextAccess, Expr, FunctionCall, Grouping, IndexAccess, Literal, Logical, Star, Unary} from "./ast";
import * as data from "./data";
import { ErrorType, ExpressionError, MAX_PARSER_DEPTH } from "./errors";
import { ParseContext, validateFunction } from "./funcs";
import { FunctionInfo } from "./funcs/info";
import { Token, TokenType } from "./lexer";
import {ErrorType, ExpressionError, MAX_PARSER_DEPTH} from "./errors";
import {ParseContext, validateFunction} from "./funcs";
import {FunctionInfo} from "./funcs/info";
import {Token, TokenType} from "./lexer";
export class Parser {
private extContexts: Map<string, boolean>;
@@ -32,11 +21,7 @@ export class Parser {
* @param extensionContexts Available context names
* @param extensionFunctions Available functions (beyond the built-in ones)
*/
constructor(
private tokens: Token[],
extensionContexts: string[],
extensionFunctions: FunctionInfo[]
) {
constructor(private tokens: Token[], extensionContexts: string[], extensionFunctions: FunctionInfo[]) {
this.extContexts = new Map<string, boolean>();
this.extFuncs = new Map();
@@ -44,9 +29,9 @@ export class Parser {
this.extContexts.set(contextName.toLowerCase(), true);
}
for (const { name, func } of extensionFunctions.map((x) => ({
for (const {name, func} of extensionFunctions.map(x => ({
name: x.name,
func: x,
func: x
}))) {
this.extFuncs.set(name.toLowerCase(), func);
}
@@ -54,7 +39,7 @@ export class Parser {
this.context = {
allowUnknownKeywords: false,
extensionContexts: this.extContexts,
extensionFunctions: this.extFuncs,
extensionFunctions: this.extFuncs
};
}
@@ -65,7 +50,7 @@ export class Parser {
if (this.atEnd()) {
return result;
}
result = this.expression();
if (!this.atEnd()) {
@@ -151,14 +136,7 @@ export class Parser {
// ! is higher precedence than >, >=, <, <=
let expr = this.unary();
while (
this.match(
TokenType.GREATER,
TokenType.GREATER_EQUAL,
TokenType.LESS,
TokenType.LESS_EQUAL
)
) {
while (this.match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) {
const operator = this.previous();
const right = this.unary();
@@ -191,11 +169,7 @@ export class Parser {
let depthIncreased = 0;
if (
expr instanceof Grouping ||
expr instanceof FunctionCall ||
expr instanceof ContextAccess
) {
if (expr instanceof Grouping || expr instanceof FunctionCall || expr instanceof ContextAccess) {
let cont = true;
while (cont) {
switch (true) {
@@ -207,10 +181,7 @@ export class Parser {
indexExpr = this.expression();
}
this.consume(
TokenType.RIGHT_BRACKET,
ErrorType.ErrorUnexpectedSymbol
);
this.consume(TokenType.RIGHT_BRACKET, ErrorType.ErrorUnexpectedSymbol);
// Track depth
this.incrDepth();
@@ -225,17 +196,11 @@ export class Parser {
if (this.match(TokenType.IDENTIFIER)) {
let property = this.previous();
expr = new IndexAccess(
expr,
new Literal(new data.StringData(property.lexeme))
);
expr = new IndexAccess(expr, new Literal(new data.StringData(property.lexeme)));
} else if (this.match(TokenType.STAR)) {
expr = new IndexAccess(expr, new Star());
} else {
throw this.buildError(
ErrorType.ErrorUnexpectedSymbol,
this.peek()
);
throw this.buildError(ErrorType.ErrorUnexpectedSymbol, this.peek());
}
break;
@@ -307,20 +272,14 @@ export class Parser {
const expr = this.expression();
if (this.atEnd()) {
throw this.buildError(
ErrorType.ErrorUnexpectedEndOfExpression,
this.previous()
); // Back up to get the last token before the EOF
throw this.buildError(ErrorType.ErrorUnexpectedEndOfExpression, this.previous()); // Back up to get the last token before the EOF
}
this.consume(TokenType.RIGHT_PAREN, ErrorType.ErrorUnexpectedSymbol);
return new Grouping(expr);
case this.atEnd():
throw this.buildError(
ErrorType.ErrorUnexpectedEndOfExpression,
this.previous()
); // Back up to get the last token before the EOF
throw this.buildError(ErrorType.ErrorUnexpectedEndOfExpression, this.previous()); // Back up to get the last token before the EOF
}
throw this.buildError(ErrorType.ErrorUnexpectedSymbol, this.peek());
+34 -37
View File
@@ -1,5 +1,5 @@
import { BooleanData, ExpressionData, NumberData, StringData } from "./data";
import { coerceTypes, toUpperSpecial } from "./result";
import {BooleanData, ExpressionData, NumberData, StringData} from "./data";
import {coerceTypes, toUpperSpecial} from "./result";
describe("coerceTypes", () => {
const tests: {
@@ -13,52 +13,52 @@ describe("coerceTypes", () => {
}[] = [
{
name: "number-bool",
data: { l: new NumberData(1), r: new BooleanData(true) },
data: {l: new NumberData(1), r: new BooleanData(true)},
wantL: new NumberData(1),
wantR: new NumberData(1),
wantR: new NumberData(1)
},
{
name: "number-bool-false",
data: { l: new NumberData(1), r: new BooleanData(false) },
data: {l: new NumberData(1), r: new BooleanData(false)},
wantL: new NumberData(1),
wantR: new NumberData(0),
wantR: new NumberData(0)
},
{
name: "bool-number-false",
data: { l: new BooleanData(false), r: new NumberData(1) },
data: {l: new BooleanData(false), r: new NumberData(1)},
wantL: new NumberData(0),
wantR: new NumberData(1),
wantR: new NumberData(1)
},
{
name: "number-number",
data: { l: new NumberData(1), r: new NumberData(2) },
data: {l: new NumberData(1), r: new NumberData(2)},
wantL: new NumberData(1),
wantR: new NumberData(2),
wantR: new NumberData(2)
},
{
name: "string-string",
data: { l: new StringData("a"), r: new StringData("b") },
data: {l: new StringData("a"), r: new StringData("b")},
wantL: new StringData("a"),
wantR: new StringData("b"),
wantR: new StringData("b")
},
{
name: "string-number",
data: { l: new StringData("a"), r: new NumberData(1) },
data: {l: new StringData("a"), r: new NumberData(1)},
wantL: new NumberData(NaN),
wantR: new NumberData(1),
wantR: new NumberData(1)
},
{
name: "number-string",
data: { l: new NumberData(1), r: new StringData("a") },
data: {l: new NumberData(1), r: new StringData("a")},
wantL: new NumberData(1),
wantR: new NumberData(NaN),
wantR: new NumberData(NaN)
},
{
name: "bool-bool",
data: { l: new BooleanData(false), r: new BooleanData(true) },
data: {l: new BooleanData(false), r: new BooleanData(true)},
wantL: new BooleanData(false),
wantR: new BooleanData(true),
},
wantR: new BooleanData(true)
}
];
test.each(tests)(
@@ -66,9 +66,9 @@ describe("coerceTypes", () => {
({
data,
wantL,
wantR,
wantR
}: {
data: { l: ExpressionData; r: ExpressionData };
data: {l: ExpressionData; r: ExpressionData};
wantL: ExpressionData;
wantR: ExpressionData;
}) => {
@@ -80,22 +80,19 @@ describe("coerceTypes", () => {
});
describe("toUpperSpecial", () => {
const tests: { input: string; want: string }[] = [
{ input: "", want: "" },
{ input: "abc", want: "ABC" },
{ input: "ıabc", want: "ıABC" },
{ input: "ııabc", want: "ııABC" },
{ input: "abcı", want: "ABCı" },
{ input: "abcıı", want: "ABCıı" },
{ input: "abcıdef", want: "ABCıDEF" },
{ input: "abcııdef", want: "ABCııDEF" },
{ input: "abcıdefıghi", want: "ABCıDEFıGHI" },
const tests: {input: string; want: string}[] = [
{input: "", want: ""},
{input: "abc", want: "ABC"},
{input: "ıabc", want: "ıABC"},
{input: "ııabc", want: "ııABC"},
{input: "abcı", want: "ABCı"},
{input: "abcıı", want: "ABCıı"},
{input: "abcıdef", want: "ABCıDEF"},
{input: "abcııdef", want: "ABCııDEF"},
{input: "abcıdefıghi", want: "ABCıDEFıGHI"}
];
test.each(tests)(
"$input",
({ input, want }: { input: string; want: string }) => {
expect(toUpperSpecial(input)).toEqual(want);
}
);
test.each(tests)("$input", ({input, want}: {input: string; want: string}) => {
expect(toUpperSpecial(input)).toEqual(want);
});
});
+3 -12
View File
@@ -76,10 +76,7 @@ export function coerceTypes(
// Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3.
// Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives.
export function equals(
lhs: data.ExpressionData,
rhs: data.ExpressionData
): boolean {
export function equals(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean {
let [lv, rv] = coerceTypes(lhs, rhs);
if (lv.kind != rv.kind) {
@@ -125,10 +122,7 @@ export function equals(
// Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3.
// Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives.
export function greaterThan(
lhs: data.ExpressionData,
rhs: data.ExpressionData
): boolean {
export function greaterThan(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean {
const [lv, rv] = coerceTypes(lhs, rhs);
if (lv.kind != rv.kind) {
@@ -166,10 +160,7 @@ export function greaterThan(
// Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3.
// Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives.
export function lessThan(
lhs: data.ExpressionData,
rhs: data.ExpressionData
): boolean {
export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean {
const [lv, rv] = coerceTypes(lhs, rhs);
if (lv.kind != rv.kind) {
+22 -42
View File
@@ -1,14 +1,14 @@
import * as fs from "fs";
import * as path from "path";
import { Expr } from "./ast";
import {Expr} from "./ast";
import * as data from "./data";
import { kindStr } from "./data/expressiondata";
import { replacer } from "./data/replacer";
import { reviver } from "./data/reviver";
import { ExpressionError } from "./errors";
import { Evaluator } from "./evaluator";
import { Lexer, Result } from "./lexer";
import { Parser } from "./parser";
import {kindStr} from "./data/expressiondata";
import {replacer} from "./data/replacer";
import {reviver} from "./data/reviver";
import {ExpressionError} from "./errors";
import {Evaluator} from "./evaluator";
import {Lexer, Result} from "./lexer";
import {Parser} from "./parser";
interface TestResult {
value: data.ExpressionData;
@@ -18,11 +18,11 @@ interface TestResult {
enum TestErrorKind {
Lexing = "lexing",
Parsing = "parsing",
Evaluation = "evaluation",
Evaluation = "evaluation"
}
enum SkipOptionKind {
Typescript = "typescript",
Typescript = "typescript"
}
interface TestOptions {
@@ -83,9 +83,7 @@ describe("x-lang tests", () => {
let tests = testCases[testCaseName];
// Filter out tests that are not supported by typescript
tests = tests.filter(
(t) => !t.options?.skip?.includes(SkipOptionKind.Typescript)
);
tests = tests.filter(t => !t.options?.skip?.includes(SkipOptionKind.Typescript));
const testName = path.basename(file, ".json");
@@ -94,7 +92,7 @@ describe("x-lang tests", () => {
}
describe(testName, () => {
test.each(tests)(`${testName}: ${testCaseName} ($expr)`, (testCase) => {
test.each(tests)(`${testName}: ${testCaseName} ($expr)`, testCase => {
if (!testCase.contexts) {
testCase.contexts = new data.Dictionary();
}
@@ -114,37 +112,29 @@ describe("x-lang tests", () => {
return;
}
throw new Error(
`unexpected error lexing expression: ${e.message} ${e.stack}`
);
throw new Error(`unexpected error lexing expression: ${e.message} ${e.stack}`);
}
// Parse
const contextNames = testCase.contexts.pairs().map((x) => x.key);
const contextNames = testCase.contexts.pairs().map(x => x.key);
const parser = new Parser(result.tokens, contextNames, []);
let expr: Expr;
try {
expr = parser.parse();
if (testCase.err?.kind === TestErrorKind.Parsing) {
throw new Error(
"expected error parsing expression, but got none"
);
throw new Error("expected error parsing expression, but got none");
}
} catch (e: any) {
// Did test expect parsing error?
if (testCase.err?.kind === TestErrorKind.Parsing) {
// Test expects parsing error
const pe = e as ExpressionError;
expect(errorWithExpression(pe, testCase.expr)).toContain(
testCase.err.value
);
expect(errorWithExpression(pe, testCase.expr)).toContain(testCase.err.value);
return;
}
throw new Error(
`unexpected error parsing expression: ${e.message} ${e.stack}`
);
throw new Error(`unexpected error parsing expression: ${e.message} ${e.stack}`);
}
// Evaluate expression
@@ -158,28 +148,20 @@ describe("x-lang tests", () => {
}
if (testCase.err?.kind === TestErrorKind.Evaluation) {
throw new Error(
"expected error evaluating expression, but got none"
);
throw new Error("expected error evaluating expression, but got none");
}
expect(kindStr(result.kind)).toBe(testCase.result.kind);
expect(JSON.stringify(result, replacer)).toEqual(
JSON.stringify(testCase.result.value, replacer)
);
expect(JSON.stringify(result, replacer)).toEqual(JSON.stringify(testCase.result.value, replacer));
} catch (e: any) {
if (testCase.err?.kind === TestErrorKind.Evaluation) {
const pe = e as ExpressionError;
expect(errorWithExpression(pe, testCase.expr)).toContain(
testCase.err.value
);
expect(errorWithExpression(pe, testCase.expr)).toContain(testCase.err.value);
return;
}
throw new Error(
`unexpected error evaluating expression: ${e.message} ${e.stack}`
);
throw new Error(`unexpected error evaluating expression: ${e.message} ${e.stack}`);
}
});
});
@@ -189,9 +171,7 @@ describe("x-lang tests", () => {
function errorWithExpression(e: ExpressionError, expr: string): string {
if (e.pos !== undefined) {
return `${e.message}. Located at position ${
e.pos.column + 1
} within expression: ${expr}`;
return `${e.message}. Located at position ${e.pos.column + 1} within expression: ${expr}`;
}
return `${e.message}. Located within expression: ${expr}`;