Lint the expressions package
This commit is contained in:
@@ -97,7 +97,7 @@ export class IndexAccess extends Expr {
|
||||
}
|
||||
|
||||
export class Star extends Expr {
|
||||
accept<R>(v: ExprVisitor<R>): R {
|
||||
accept<R>(): R {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ export function complete(
|
||||
extensionFunctions
|
||||
);
|
||||
const expr = p.parse();
|
||||
if (!expr) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ev = new Evaluator(expr, context, functions);
|
||||
const result = ev.evaluate();
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import {Expr} from "./ast";
|
||||
import * as data from "./data";
|
||||
import {ExpressionEvaluationError} from "./errors";
|
||||
import {Evaluator} from "./evaluator";
|
||||
import {Lexer} from "./lexer";
|
||||
import {Parser} from "./parser";
|
||||
|
||||
function assertDefined(x: Expr | undefined): asserts x is Expr {
|
||||
expect(x).toBeDefined();
|
||||
}
|
||||
|
||||
describe("evaluator", () => {
|
||||
const lexAndParse = (input: string) => {
|
||||
const lexer = new Lexer(input);
|
||||
@@ -12,6 +17,7 @@ describe("evaluator", () => {
|
||||
// Parse
|
||||
const parser = new Parser(result.tokens, ["foo"], []);
|
||||
const expr = parser.parse();
|
||||
assertDefined(expr);
|
||||
return expr;
|
||||
};
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
|
||||
}
|
||||
|
||||
// result is always assigned before we return here
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return result!;
|
||||
}
|
||||
|
||||
@@ -101,6 +102,7 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
|
||||
}
|
||||
|
||||
visitContextAccess(contextAccess: ContextAccess): data.ExpressionData {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const r = this.context.get(contextAccess.name.lexeme)!;
|
||||
return r;
|
||||
}
|
||||
@@ -114,7 +116,7 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
|
||||
try {
|
||||
idxResult = this.eval(ia.index);
|
||||
} catch (e) {
|
||||
throw new Error(`could not evaluate index for index access: ${e}`, {cause: e});
|
||||
throw new Error(`could not evaluate index for index access: ${(e as Error).message}`, {cause: e});
|
||||
}
|
||||
idx = new idxHelper(false, idxResult);
|
||||
}
|
||||
@@ -124,9 +126,9 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
|
||||
let result: data.ExpressionData;
|
||||
switch (objResult.kind) {
|
||||
case data.Kind.Array: {
|
||||
const tobjResult = objResult as data.Array;
|
||||
const tobjResult = objResult;
|
||||
if (tobjResult instanceof FilteredArray) {
|
||||
result = filteredArrayAccess(tobjResult as FilteredArray, idx);
|
||||
result = filteredArrayAccess(tobjResult, idx);
|
||||
} else {
|
||||
result = arrayAccess(tobjResult, idx);
|
||||
}
|
||||
@@ -135,7 +137,7 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
|
||||
}
|
||||
|
||||
case data.Kind.Dictionary: {
|
||||
const tobjResult = objResult as data.Dictionary;
|
||||
const tobjResult = objResult;
|
||||
result = objectAccess(tobjResult, idx);
|
||||
break;
|
||||
}
|
||||
@@ -170,7 +172,7 @@ function filteredArrayAccess(fa: FilteredArray, idx: idxHelper): data.Expression
|
||||
// Check the type of the nested item
|
||||
switch (item.kind) {
|
||||
case data.Kind.Dictionary: {
|
||||
const ti = item as data.Dictionary;
|
||||
const ti = item;
|
||||
if (idx.star) {
|
||||
for (const v of ti.values()) {
|
||||
result.add(v);
|
||||
@@ -186,7 +188,7 @@ function filteredArrayAccess(fa: FilteredArray, idx: idxHelper): data.Expression
|
||||
}
|
||||
|
||||
case data.Kind.Array: {
|
||||
const ti = item as data.Array;
|
||||
const ti = item;
|
||||
if (idx.star) {
|
||||
for (const v of ti.values()) {
|
||||
result.add(v);
|
||||
|
||||
@@ -5,12 +5,15 @@ export class idxHelper {
|
||||
public readonly int: number | undefined;
|
||||
|
||||
constructor(public readonly star: boolean, idx: ExpressionData | undefined) {
|
||||
if (!idx) {
|
||||
return;
|
||||
}
|
||||
if (!star) {
|
||||
if (idx!.primitive) {
|
||||
this.str = idx!.coerceString();
|
||||
if (idx.primitive) {
|
||||
this.str = idx.coerceString();
|
||||
}
|
||||
|
||||
let f = idx!.number();
|
||||
let f = idx.number();
|
||||
if (!isNaN(f) && isFinite(f) && f >= 0) {
|
||||
f = Math.floor(f);
|
||||
this.int = f;
|
||||
|
||||
@@ -57,6 +57,7 @@ export function tokenString(tok: Token): string {
|
||||
case TokenType.NUMBER:
|
||||
return tok.lexeme;
|
||||
case TokenType.STRING:
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return tok.value!.toString();
|
||||
default:
|
||||
return tok.lexeme;
|
||||
|
||||
+10
-11
@@ -9,8 +9,8 @@ export class Parser {
|
||||
private extContexts: Map<string, boolean>;
|
||||
private extFuncs: Map<string, FunctionInfo>;
|
||||
|
||||
private offset: number = 0;
|
||||
private depth: number = 0;
|
||||
private offset = 0;
|
||||
private depth = 0;
|
||||
|
||||
private context: ParseContext;
|
||||
|
||||
@@ -43,15 +43,13 @@ export class Parser {
|
||||
};
|
||||
}
|
||||
|
||||
public parse(): Expr {
|
||||
let result!: Expr;
|
||||
|
||||
public parse(): Expr | undefined {
|
||||
// No tokens
|
||||
if (this.atEnd()) {
|
||||
return result;
|
||||
return;
|
||||
}
|
||||
|
||||
result = this.expression();
|
||||
const result = this.expression();
|
||||
|
||||
if (!this.atEnd()) {
|
||||
throw this.buildError(ErrorType.ErrorUnexpectedSymbol, this.peek());
|
||||
@@ -173,7 +171,7 @@ export class Parser {
|
||||
let cont = true;
|
||||
while (cont) {
|
||||
switch (true) {
|
||||
case this.match(TokenType.LEFT_BRACKET):
|
||||
case this.match(TokenType.LEFT_BRACKET): {
|
||||
let indexExpr: Expr;
|
||||
if (this.match(TokenType.STAR)) {
|
||||
indexExpr = new Star();
|
||||
@@ -188,6 +186,7 @@ export class Parser {
|
||||
depthIncreased++;
|
||||
expr = new IndexAccess(expr, indexExpr);
|
||||
break;
|
||||
}
|
||||
|
||||
case this.match(TokenType.DOT):
|
||||
// Track depth
|
||||
@@ -195,7 +194,7 @@ export class Parser {
|
||||
depthIncreased++;
|
||||
|
||||
if (this.match(TokenType.IDENTIFIER)) {
|
||||
let property = this.previous();
|
||||
const property = this.previous();
|
||||
expr = new IndexAccess(expr, new Literal(new data.StringData(property.lexeme), property));
|
||||
} else if (this.match(TokenType.STAR)) {
|
||||
expr = new IndexAccess(expr, new Star());
|
||||
@@ -268,7 +267,7 @@ export class Parser {
|
||||
case this.match(TokenType.STRING):
|
||||
return new Literal(new data.StringData(this.previous().value as string), this.previous());
|
||||
|
||||
case this.match(TokenType.LEFT_PAREN):
|
||||
case this.match(TokenType.LEFT_PAREN): {
|
||||
const expr = this.expression();
|
||||
|
||||
if (this.atEnd()) {
|
||||
@@ -277,7 +276,7 @@ export class Parser {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
+23
-14
@@ -8,10 +8,10 @@ export function falsy(d: data.ExpressionData): boolean {
|
||||
case data.Kind.Boolean:
|
||||
return !d.value;
|
||||
|
||||
case data.Kind.Number:
|
||||
case data.Kind.Number: {
|
||||
const dv = d.value;
|
||||
return dv === 0 || isNaN(dv);
|
||||
|
||||
}
|
||||
case data.Kind.String:
|
||||
return d.value.length === 0;
|
||||
}
|
||||
@@ -77,7 +77,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 {
|
||||
let [lv, rv] = coerceTypes(lhs, rhs);
|
||||
const [lv, rv] = coerceTypes(lhs, rhs);
|
||||
|
||||
if (lv.kind != rv.kind) {
|
||||
return false;
|
||||
@@ -89,7 +89,7 @@ export function equals(lhs: data.ExpressionData, rhs: data.ExpressionData): bool
|
||||
return true;
|
||||
|
||||
// Number, Number
|
||||
case data.Kind.Number:
|
||||
case data.Kind.Number: {
|
||||
const ld = lv.value;
|
||||
const rd = (rv as data.NumberData).value;
|
||||
if (isNaN(ld) || isNaN(rd)) {
|
||||
@@ -97,18 +97,21 @@ export function equals(lhs: data.ExpressionData, rhs: data.ExpressionData): bool
|
||||
}
|
||||
|
||||
return ld == rd;
|
||||
}
|
||||
|
||||
// String, String
|
||||
case data.Kind.String:
|
||||
case data.Kind.String: {
|
||||
const ls = lv.value;
|
||||
const rs = (rv as data.StringData).value;
|
||||
return toUpperSpecial(ls) === toUpperSpecial(rs);
|
||||
}
|
||||
|
||||
// Boolean, Boolean
|
||||
case data.Kind.Boolean:
|
||||
case data.Kind.Boolean: {
|
||||
const lb = lv.value;
|
||||
const rb = (rv as data.BooleanData).value;
|
||||
return lb == rb;
|
||||
}
|
||||
|
||||
// Object, Object
|
||||
case data.Kind.Dictionary:
|
||||
@@ -131,7 +134,7 @@ export function greaterThan(lhs: data.ExpressionData, rhs: data.ExpressionData):
|
||||
|
||||
switch (lv.kind) {
|
||||
// Number, Number
|
||||
case data.Kind.Number:
|
||||
case data.Kind.Number: {
|
||||
const lf = lv.value;
|
||||
const rf = (rv as data.NumberData).value;
|
||||
if (isNaN(lf) || isNaN(rf)) {
|
||||
@@ -139,20 +142,23 @@ export function greaterThan(lhs: data.ExpressionData, rhs: data.ExpressionData):
|
||||
}
|
||||
|
||||
return lf > rf;
|
||||
}
|
||||
|
||||
// String, String
|
||||
case data.Kind.String:
|
||||
case data.Kind.String: {
|
||||
let ls = lv.value;
|
||||
let rs = (rv as data.StringData).value;
|
||||
ls = toUpperSpecial(ls);
|
||||
rs = toUpperSpecial(rs);
|
||||
return ls > rs;
|
||||
}
|
||||
|
||||
// Boolean, Boolean
|
||||
case data.Kind.Boolean:
|
||||
const lb = (lv as data.BooleanData).value;
|
||||
case data.Kind.Boolean: {
|
||||
const lb = lv.value;
|
||||
const rb = (rv as data.BooleanData).value;
|
||||
return lb && !rb;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -169,7 +175,7 @@ export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): bo
|
||||
|
||||
switch (lv.kind) {
|
||||
// Number, Number
|
||||
case data.Kind.Number:
|
||||
case data.Kind.Number: {
|
||||
const lf = lv.value;
|
||||
const rf = (rv as data.NumberData).value;
|
||||
if (isNaN(lf) || isNaN(rf)) {
|
||||
@@ -177,20 +183,23 @@ export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): bo
|
||||
}
|
||||
|
||||
return lf < rf;
|
||||
}
|
||||
|
||||
// String, String
|
||||
case data.Kind.String:
|
||||
case data.Kind.String: {
|
||||
let ls = lv.value;
|
||||
let rs = (rv as data.StringData).value;
|
||||
ls = toUpperSpecial(ls);
|
||||
rs = toUpperSpecial(rs);
|
||||
return ls < rs;
|
||||
}
|
||||
|
||||
// Boolean, Boolean
|
||||
case data.Kind.Boolean:
|
||||
case data.Kind.Boolean: {
|
||||
const lb = lv.value;
|
||||
const rb = (rv as data.BooleanData).value;
|
||||
return !lb && rb;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -200,7 +209,7 @@ export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): bo
|
||||
export function toUpperSpecial(s: string): string {
|
||||
const sb: string[] = [];
|
||||
let i = 0;
|
||||
let len = s.length;
|
||||
const len = s.length;
|
||||
let found = s.indexOf("ı");
|
||||
while (i < len) {
|
||||
if (i < found) {
|
||||
|
||||
@@ -42,7 +42,7 @@ interface TestCase {
|
||||
options: TestOptions;
|
||||
}
|
||||
|
||||
function testCaseReviver(key: string, val: any): any {
|
||||
function testCaseReviver(key: string, val: unknown): unknown {
|
||||
if (key === "contexts") {
|
||||
const tmp = JSON.stringify(val);
|
||||
return JSON.parse(tmp, reviver);
|
||||
@@ -105,27 +105,28 @@ describe("x-lang tests", () => {
|
||||
if (testCase.err && testCase.err.kind === TestErrorKind.Lexing) {
|
||||
throw new Error("expected error lexing expression, but got none");
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
// Did test expect lexing error? If so, compare error message.
|
||||
if (testCase.err && testCase.err.kind === TestErrorKind.Lexing) {
|
||||
expect(e.message).toContain(testCase.err.value);
|
||||
expect(err.message).toContain(testCase.err.value);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`unexpected error lexing expression: ${e.message} ${e.stack}`);
|
||||
throw new Error(`unexpected error lexing expression: ${err.message} ${err.stack || ""}`);
|
||||
}
|
||||
|
||||
// Parse
|
||||
const contextNames = testCase.contexts.pairs().map(x => x.key);
|
||||
const parser = new Parser(result.tokens, contextNames, []);
|
||||
let expr: Expr;
|
||||
let expr: Expr | undefined;
|
||||
try {
|
||||
expr = parser.parse();
|
||||
|
||||
if (testCase.err?.kind === TestErrorKind.Parsing) {
|
||||
throw new Error("expected error parsing expression, but got none");
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
// Did test expect parsing error?
|
||||
if (testCase.err?.kind === TestErrorKind.Parsing) {
|
||||
// Test expects parsing error
|
||||
@@ -133,8 +134,8 @@ describe("x-lang tests", () => {
|
||||
expect(errorWithExpression(pe, testCase.expr)).toContain(testCase.err.value);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`unexpected error parsing expression: ${e.message} ${e.stack}`);
|
||||
const err = e as Error;
|
||||
throw new Error(`unexpected error parsing expression: ${err.message} ${err.stack || ""}`);
|
||||
}
|
||||
|
||||
// Evaluate expression
|
||||
@@ -154,14 +155,15 @@ describe("x-lang tests", () => {
|
||||
expect(kindStr(result.kind)).toBe(testCase.result.kind);
|
||||
|
||||
expect(JSON.stringify(result, replacer)).toEqual(JSON.stringify(testCase.result.value, replacer));
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
if (testCase.err?.kind === TestErrorKind.Evaluation) {
|
||||
const pe = e as ExpressionError;
|
||||
expect(errorWithExpression(pe, testCase.expr)).toContain(testCase.err.value);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`unexpected error evaluating expression: ${e.message} ${e.stack}`);
|
||||
const err = e as Error;
|
||||
throw new Error(`unexpected error evaluating expression: ${err.message} ${err.stack || ""}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user