Skip to content

Commit 18ddad5

Browse files
committed
id() and deepcopy()
1 parent 89591d9 commit 18ddad5

File tree

4 files changed

+90
-28
lines changed

4 files changed

+90
-28
lines changed

src/vimscript/expression/build.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ import {
2525
StringExpression,
2626
} from './types';
2727

28+
class UniqueIdGenerator {
29+
private prefix: string;
30+
private nextId: number = 1;
31+
32+
constructor(prefix: string) {
33+
this.prefix = prefix;
34+
}
35+
36+
public next(): string {
37+
return `${this.prefix}${this.nextId++}`;
38+
}
39+
}
40+
41+
const listIdGen = new UniqueIdGenerator('list');
42+
const dictIdGen = new UniqueIdGenerator('dict');
43+
const funcIdGen = new UniqueIdGenerator('func');
44+
const blobIdGen = new UniqueIdGenerator('blob');
45+
2846
export function int(value: number): NumberValue {
2947
return {
3048
type: 'number',
@@ -54,13 +72,15 @@ export function list(items: Value[]): ListValue {
5472
return {
5573
type: 'list',
5674
items,
75+
id: listIdGen.next(),
5776
};
5877
}
5978

6079
export function dictionary(items: Map<string, Value>): DictionaryValue {
6180
return {
6281
type: 'dictionary',
6382
items,
83+
id: dictIdGen.next(),
6484
};
6585
}
6686

@@ -73,13 +93,15 @@ export function funcref(args: {
7393
return {
7494
type: 'funcref',
7595
...args,
96+
id: funcIdGen.next(),
7697
};
7798
}
7899

79100
export function blob(data: Uint8Array<ArrayBuffer>): BlobValue {
80101
return {
81102
type: 'blob',
82103
data,
104+
id: blobIdGen.next(),
83105
};
84106
}
85107

src/vimscript/expression/evaluate.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,9 @@ export class EvaluationContext {
167167
case 'number':
168168
case 'float':
169169
case 'string':
170-
case 'blob':
171170
return expression;
171+
case 'blob':
172+
return blob(expression.data);
172173
case 'list':
173174
return list(expression.items.map((x) => this.evaluate(x)));
174175
case 'dictionary': {
@@ -590,6 +591,7 @@ export class EvaluationContext {
590591
)
591592
);
592593
case 'is':
594+
// NOTE: `id` field should match if and only if they are the same list
593595
return lhs.items === rhs.items;
594596
default:
595597
throw VimError.fromCode(ErrorCode.InvalidOperationForList);
@@ -613,6 +615,7 @@ export class EvaluationContext {
613615
)
614616
);
615617
case 'is':
618+
// NOTE: `id` field should match if and only if they are the same dictionary
616619
return lhs.items === rhs.items;
617620
default:
618621
throw VimError.fromCode(ErrorCode.InvalidOperationForDictionary);
@@ -628,6 +631,7 @@ export class EvaluationContext {
628631
case '==':
629632
return lhs.name === rhs.name && lhs.dict === rhs.dict;
630633
case 'is':
634+
// NOTE: `id` field should match if and only if they are the same funcref
631635
return lhs === rhs;
632636
default:
633637
throw VimError.fromCode(ErrorCode.InvalidOperationForFuncrefs);
@@ -644,6 +648,7 @@ export class EvaluationContext {
644648
const [_lhs, _rhs] = [new Uint8Array(lhs.data), new Uint8Array(rhs.data)];
645649
return _lhs.length === _rhs.length && _lhs.every((byte, idx) => byte === _rhs[idx]);
646650
case 'is':
651+
// NOTE: `id` field should match if and only if they are the same blob
647652
return lhs.data === rhs.data;
648653
default:
649654
throw VimError.fromCode(ErrorCode.InvalidOperationForBlob);
@@ -729,6 +734,18 @@ export class EvaluationContext {
729734
return false;
730735
};
731736

737+
const copy = (arg: Value, deep: boolean): Value => {
738+
switch (arg.type) {
739+
case 'list':
740+
return list(deep ? arg.items.map((item) => copy(item, true)) : arg.items);
741+
case 'dictionary':
742+
return dictionary(
743+
new Map(deep ? [...arg.items].map(([k, v]) => [k, copy(v, true)]) : arg.items),
744+
);
745+
}
746+
return arg;
747+
};
748+
732749
const getArgs = (min: number, max?: number) => {
733750
if (max === undefined) {
734751
max = min;
@@ -892,13 +909,7 @@ export class EvaluationContext {
892909
}
893910
case 'copy': {
894911
const [x] = getArgs(1);
895-
switch (x?.type) {
896-
case 'list':
897-
return list([...x.items]);
898-
case 'dictionary':
899-
return dictionary(new Map(x.items));
900-
}
901-
return x!;
912+
return copy(x!, false);
902913
}
903914
case 'cos': {
904915
const [x] = getArgs(1);
@@ -953,9 +964,8 @@ export class EvaluationContext {
953964
}
954965
// TODO: cursor()
955966
case 'deepcopy': {
956-
// TODO: real deep copy once references are implemented
957967
const [x] = getArgs(1);
958-
return x!;
968+
return copy(x!, true);
959969
}
960970
case 'empty': {
961971
let [x] = getArgs(1);
@@ -1122,7 +1132,14 @@ export class EvaluationContext {
11221132
}
11231133
// TODO: hasmapto()
11241134
// TODO: histadd()/histdel()/histget()/histnr()
1125-
// TODO: id()
1135+
case 'id': {
1136+
// NOTE: Vim behaves differently (generally returning pointer addresses), but this serves the purpose
1137+
const [x] = getArgs(1);
1138+
if (x!.type === 'number' || x!.type === 'float' || x!.type === 'string') {
1139+
return str(x!.value.toString());
1140+
}
1141+
return str(x!.id);
1142+
}
11261143
case 'index': {
11271144
const [_haystack, _needle, _start, ic] = getArgs(2, 4);
11281145
const haystack: Value = _haystack!;

src/vimscript/expression/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ export type StringValue = {
1818
export type ListValue = {
1919
type: 'list';
2020
items: Value[];
21+
id: string;
2122
};
2223

2324
export type DictionaryValue = {
2425
type: 'dictionary';
2526
items: Map<string, Value>;
27+
id: string;
2628
};
2729

2830
export type FuncRefValue = {
@@ -31,11 +33,13 @@ export type FuncRefValue = {
3133
body?: (args: Value[]) => Value;
3234
arglist?: ListValue;
3335
dict?: DictionaryValue;
36+
id: string;
3437
};
3538

3639
export type BlobValue = {
3740
type: 'blob';
3841
data: Uint8Array<ArrayBuffer>;
42+
id: string;
3943
};
4044

4145
export type Value =

test/vimscript/expression.test.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,35 @@ import {
1414
bool,
1515
list,
1616
dictionary,
17+
blob,
1718
} from '../../src/vimscript/expression/build';
1819
import { EvaluationContext } from '../../src/vimscript/expression/evaluate';
1920
import { expressionParser } from '../../src/vimscript/expression/parser';
2021
import { Expression, Value } from '../../src/vimscript/expression/types';
2122
import { displayValue } from '../../src/vimscript/expression/displayValue';
2223
import { ErrorCode, VimError } from '../../src/error';
2324

25+
function removeIds(value: Value): unknown {
26+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
27+
const { id, ...rest } = value as any;
28+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
29+
const _value: any = { ...rest };
30+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
31+
if (value.type === 'list') {
32+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
33+
_value.items = value.items.map(removeIds);
34+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
35+
} else if (value.type === 'dictionary') {
36+
const items = new Map<string, unknown>();
37+
for (const [key, val] of value.items) {
38+
items.set(key, removeIds(val));
39+
}
40+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
41+
_value.items = items;
42+
}
43+
return _value;
44+
}
45+
2446
function exprTest(
2547
input: string,
2648
asserts: { expr?: Expression } & ({ value?: Value; display?: string } | { error: ErrorCode }),
@@ -37,7 +59,7 @@ function exprTest(
3759
} else {
3860
if (asserts.value !== undefined) {
3961
const ctx = new EvaluationContext(undefined);
40-
assert.deepStrictEqual(ctx.evaluate(expression), asserts.value);
62+
assert.deepStrictEqual(removeIds(ctx.evaluate(expression)), removeIds(asserts.value));
4163
}
4264
if (asserts.display !== undefined) {
4365
const ctx = new EvaluationContext(undefined);
@@ -113,28 +135,16 @@ suite('Vimscript expressions', () => {
113135

114136
suite('Blobs', () => {
115137
exprTest('0z', {
116-
expr: {
117-
type: 'blob',
118-
data: new Uint8Array([]),
119-
},
138+
value: blob(new Uint8Array([])),
120139
});
121140
exprTest('0zabcd', {
122-
expr: {
123-
type: 'blob',
124-
data: new Uint8Array([171, 205]),
125-
},
141+
value: blob(new Uint8Array([171, 205])),
126142
});
127143
exprTest('0ZABCD', {
128-
expr: {
129-
type: 'blob',
130-
data: new Uint8Array([171, 205]),
131-
},
144+
value: blob(new Uint8Array([171, 205])),
132145
});
133146
exprTest('0zAB.CD', {
134-
expr: {
135-
type: 'blob',
136-
data: new Uint8Array([171, 205]),
137-
},
147+
value: blob(new Uint8Array([171, 205])),
138148
});
139149
exprTest('0zabc', {
140150
error: ErrorCode.BlobLiteralShouldHaveAnEvenNumberOfHexCharacters,
@@ -392,6 +402,7 @@ suite('Vimscript expressions', () => {
392402

393403
exprTest('5/0', { value: int(Infinity) }); // TODO: Neovim returns `v:numbermax`
394404
exprTest('-5/0', { value: int(-Infinity) }); // TODO: Neovim returns `v:numbermin`
405+
exprTest('0/0', { value: int(NaN) }); // TODO: Neovim returns `v:numbermax`
395406

396407
// TODO: Grok what Neovim does with 5/0.0
397408

@@ -660,6 +671,8 @@ suite('Vimscript expressions', () => {
660671
exprTest('float2nr(40.0)', { value: int(40) });
661672
exprTest('float2nr(65.7)', { value: int(65) });
662673
exprTest('float2nr(-20.7)', { value: int(-20) });
674+
675+
// TODO: Infinity, -Infinity, NaN
663676
});
664677

665678
suite('fmod', () => {
@@ -691,6 +704,12 @@ suite('Vimscript expressions', () => {
691704
exprTest('has_key(#{a:1, b:2, c:3}, "d")', { value: bool(false) });
692705
});
693706

707+
suite('id', () => {
708+
exprTest('id(2+2) == id(4)', { value: bool(true) });
709+
exprTest('id(2+2) == id(5)', { value: bool(false) });
710+
// TODO: Everything else
711+
});
712+
694713
suite('index', () => {
695714
exprTest('index(["a","b","c"], "c")', { value: int(2) });
696715
exprTest('index(["a","b","c"], "k")', { value: int(-1) });

0 commit comments

Comments
 (0)