Skip to content

Commit a77ec7b

Browse files
committed
Fix a few bugs in comparison expressions
1 parent 30aa374 commit a77ec7b

File tree

3 files changed

+83
-41
lines changed

3 files changed

+83
-41
lines changed

src/error.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export enum ErrorCode {
5252
CanOnlyIndexAListDictionaryOrBlob = 689,
5353
CanOnlyCompareListWithList = 691,
5454
InvalidOperationForList = 692,
55+
InvalidOperationForFuncrefs = 694,
5556
CannotIndexAFuncref = 695,
5657
UnknownFunction_funcref = 700,
5758
InvalidTypeForLen = 701,
@@ -87,6 +88,8 @@ export enum ErrorCode {
8788
SecondArgumentOfFunction = 923,
8889
BlobLiteralShouldHaveAnEvenNumberOfHexCharacters = 973,
8990
UsingABlobAsANumber = 974,
91+
CanOnlyCompareBlobWithBlob = 977,
92+
InvalidOperationForBlob = 978,
9093
CannotModifyExistingVariable = 995,
9194
CannotLockARegister = 996,
9295
}
@@ -141,6 +144,7 @@ export const ErrorMessage: IErrorMessage = {
141144
689: 'Can only index a List, Dictionary or Blob',
142145
691: 'Can only compare List with List',
143146
692: 'Invalid operation for List',
147+
694: 'Invalid operation for Funcrefs',
144148
695: 'Cannot index a Funcref',
145149
700: 'Unknown function',
146150
701: 'Invalid type for len()',
@@ -176,6 +180,8 @@ export const ErrorMessage: IErrorMessage = {
176180
923: 'Second argument of function() must be a list or a dict',
177181
973: 'Blob literal should have an even number of hex characters',
178182
974: 'Using a Blob as a Number',
183+
977: 'Can only compare Blob with Blob',
184+
978: 'Invalid operation for Blob',
179185
995: 'Cannot modify existing variable',
180186
996: 'Cannot lock a register',
181187
};

src/vimscript/expression/evaluate.ts

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,14 @@ export class EvaluationContext {
247247
toInt(this.evaluate(expression.if)) !== 0 ? expression.then : expression.else,
248248
);
249249
case 'comparison':
250+
const _lhs = this.evaluate(expression.lhs);
251+
const _rhs = this.evaluate(expression.rhs);
250252
return bool(
251253
this.evaluateComparison(
252254
expression.operator,
253255
expression.matchCase ?? configuration.ignorecase,
254-
expression.lhs,
255-
expression.rhs,
256+
_lhs,
257+
_rhs,
256258
),
257259
);
258260
default: {
@@ -511,81 +513,79 @@ export class EvaluationContext {
511513
private evaluateComparison(
512514
operator: ComparisonOp,
513515
matchCase: boolean,
514-
lhsExpr: Expression,
515-
rhsExpr: Expression,
516+
lhs: Value,
517+
rhs: Value,
516518
): boolean {
517519
switch (operator) {
518520
case '==':
519-
return this.evaluateBasicComparison('==', matchCase, lhsExpr, rhsExpr);
521+
return this.evaluateBasicComparison('==', matchCase, lhs, rhs);
520522
case '!=':
521-
return !this.evaluateBasicComparison('==', matchCase, lhsExpr, rhsExpr);
523+
return !this.evaluateBasicComparison('==', matchCase, lhs, rhs);
522524
case '>':
523-
return this.evaluateBasicComparison('>', matchCase, lhsExpr, rhsExpr);
525+
return this.evaluateBasicComparison('>', matchCase, lhs, rhs);
524526
case '>=':
525527
return (
526-
this.evaluateBasicComparison('>', matchCase, lhsExpr, rhsExpr) ||
527-
this.evaluateBasicComparison('==', matchCase, lhsExpr, rhsExpr)
528+
this.evaluateBasicComparison('>', matchCase, lhs, rhs) ||
529+
this.evaluateBasicComparison('==', matchCase, lhs, rhs)
528530
);
529531
case '<':
530-
return this.evaluateBasicComparison('>', matchCase, rhsExpr, lhsExpr);
532+
return this.evaluateBasicComparison('>', matchCase, rhs, lhs);
531533
case '<=':
532-
return !this.evaluateBasicComparison('>', matchCase, lhsExpr, rhsExpr);
534+
return !this.evaluateBasicComparison('>', matchCase, lhs, rhs);
533535
case '=~':
534-
return this.evaluateBasicComparison('=~', matchCase, lhsExpr, rhsExpr);
536+
return this.evaluateBasicComparison('=~', matchCase, lhs, rhs);
535537
case '!~':
536-
return !this.evaluateBasicComparison('=~', matchCase, lhsExpr, rhsExpr);
538+
return !this.evaluateBasicComparison('=~', matchCase, lhs, rhs);
537539
case 'is':
538-
return this.evaluateBasicComparison('is', matchCase, lhsExpr, rhsExpr);
540+
return this.evaluateBasicComparison('is', matchCase, lhs, rhs);
539541
case 'isnot':
540-
return !this.evaluateBasicComparison('is', matchCase, lhsExpr, rhsExpr);
542+
return !this.evaluateBasicComparison('is', matchCase, lhs, rhs);
541543
}
542544
}
543545

544546
private evaluateBasicComparison(
545547
operator: '==' | '>' | '=~' | 'is',
546548
matchCase: boolean,
547-
lhsExpr: Expression,
548-
rhsExpr: Expression,
549+
lhs: Value,
550+
rhs: Value,
549551
topLevel: boolean = true,
550552
): boolean {
551-
if (operator === 'is' && lhsExpr.type !== rhsExpr.type) {
553+
if (operator === 'is' && lhs.type !== rhs.type) {
552554
return false;
553555
}
554556

555-
if (lhsExpr.type === 'list') {
556-
if (rhsExpr.type === 'list') {
557+
if (lhs.type === 'list') {
558+
if (rhs.type === 'list') {
557559
switch (operator) {
558560
case '==':
561+
const rhsItems = rhs.items;
559562
return (
560-
lhsExpr.items.length === rhsExpr.items.length &&
561-
lhsExpr.items.every((left, idx) =>
562-
this.evaluateBasicComparison('==', matchCase, left, rhsExpr.items[idx], false),
563+
lhs.items.length === rhsItems.length &&
564+
lhs.items.every((left, idx) =>
565+
this.evaluateBasicComparison('==', matchCase, left, rhsItems[idx], false),
563566
)
564567
);
565568
case 'is':
566-
return lhsExpr.items === rhsExpr.items;
569+
return lhs.items === rhs.items;
567570
default:
568571
throw VimError.fromCode(ErrorCode.InvalidOperationForList);
569572
}
570573
} else {
571574
throw VimError.fromCode(ErrorCode.CanOnlyCompareListWithList);
572575
}
573-
} else if (rhsExpr.type === 'list') {
576+
} else if (rhs.type === 'list') {
574577
throw VimError.fromCode(ErrorCode.CanOnlyCompareListWithList);
575-
} else if (lhsExpr.type === 'dictionary') {
576-
if (rhsExpr.type === 'dictionary') {
577-
const [lhs, rhs] = [this.evaluate(lhsExpr), this.evaluate(rhsExpr)] as [
578-
DictionaryValue,
579-
DictionaryValue,
580-
];
578+
} else if (lhs.type === 'dict_val') {
579+
if (rhs.type === 'dict_val') {
581580
switch (operator) {
582581
case '==':
582+
const rhsItems = rhs.items;
583583
return (
584-
lhs.items.size === rhs.items.size &&
584+
lhs.items.size === rhsItems.size &&
585585
[...lhs.items.entries()].every(
586586
([key, value]) =>
587-
rhs.items.has(key) &&
588-
this.evaluateBasicComparison('==', matchCase, value, rhs.items.get(key)!, false),
587+
rhsItems.has(key) &&
588+
this.evaluateBasicComparison('==', matchCase, value, rhsItems.get(key)!, false),
589589
)
590590
);
591591
case 'is':
@@ -596,14 +596,43 @@ export class EvaluationContext {
596596
} else {
597597
throw VimError.fromCode(ErrorCode.CanOnlyCompareDictionaryWithDictionary);
598598
}
599-
} else if (rhsExpr.type === 'dictionary') {
599+
} else if (rhs.type === 'dict_val') {
600600
throw VimError.fromCode(ErrorCode.CanOnlyCompareDictionaryWithDictionary);
601+
} else if (lhs.type === 'funcref') {
602+
if (rhs.type === 'funcref') {
603+
switch (operator) {
604+
case '==':
605+
return lhs.name === rhs.name && lhs.dict === rhs.dict;
606+
case 'is':
607+
return lhs === rhs;
608+
default:
609+
throw VimError.fromCode(ErrorCode.InvalidOperationForFuncrefs);
610+
}
611+
} else {
612+
return false;
613+
}
614+
} else if (rhs.type === 'funcref') {
615+
return false;
616+
} else if (lhs.type === 'blob') {
617+
if (rhs.type === 'blob') {
618+
switch (operator) {
619+
case '==':
620+
const [_lhs, _rhs] = [new Uint8Array(lhs.data), new Uint8Array(rhs.data)];
621+
return _lhs.length === _rhs.length && _lhs.every((byte, idx) => byte === _rhs[idx]);
622+
case 'is':
623+
return lhs.data === rhs.data;
624+
default:
625+
throw VimError.fromCode(ErrorCode.InvalidOperationForBlob);
626+
}
627+
} else {
628+
throw VimError.fromCode(ErrorCode.CanOnlyCompareBlobWithBlob);
629+
}
630+
} else if (rhs.type === 'blob') {
631+
throw VimError.fromCode(ErrorCode.CanOnlyCompareBlobWithBlob);
601632
} else {
602-
let [lhs, rhs] = [this.evaluate(lhsExpr), this.evaluate(rhsExpr)] as [
603-
NumberValue | StringValue,
604-
NumberValue | StringValue,
605-
];
606-
if (lhs.type === 'number' || rhs.type === 'number') {
633+
if (lhs.type === 'float' || rhs.type === 'float') {
634+
[lhs, rhs] = [float(toFloat(lhs)), float(toFloat(rhs))];
635+
} else if (lhs.type === 'number' || rhs.type === 'number') {
607636
if (topLevel) {
608637
// Strings are automatically coerced to numbers, except within a list/dict
609638
// i.e. 4 == "4" but [4] != ["4"]

test/vimscript/expression.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,12 +426,19 @@ suite('Vimscript expressions', () => {
426426
exprTest("'abc' ==? 'Abc'", { value: bool(true) });
427427
});
428428

429-
suite('Misc', () => {
429+
suite('Different types', () => {
430430
exprTest("4 == '4'", { value: bool(true) });
431431
exprTest("4 is '4'", { value: bool(false) });
432432
exprTest('0 is []', { value: bool(false) });
433433
exprTest('0 is {}', { value: bool(false) });
434434
exprTest('[4] == ["4"]', { value: bool(false) });
435+
exprTest('3.2 > 3', { value: bool(true) });
436+
exprTest('5 == [5]', { error: ErrorCode.CanOnlyCompareListWithList });
437+
exprTest('[] == {}', { error: ErrorCode.CanOnlyCompareListWithList });
438+
exprTest('{} == []', { error: ErrorCode.CanOnlyCompareListWithList });
439+
exprTest('{} == 10', { error: ErrorCode.CanOnlyCompareDictionaryWithDictionary });
440+
exprTest('0 == 0z00', { error: ErrorCode.CanOnlyCompareBlobWithBlob });
441+
exprTest('2 == function("abs")', { value: bool(false) });
435442
});
436443
});
437444

0 commit comments

Comments
 (0)