Skip to content

Commit a35b978

Browse files
committed
Support indexing with :let
1 parent 256422b commit a35b978

File tree

4 files changed

+77
-24
lines changed

4 files changed

+77
-24
lines changed

src/cmd_line/commands/let.ts

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import {
88
add,
99
concat,
1010
divide,
11+
int,
1112
modulo,
1213
multiply,
1314
str,
1415
subtract,
1516
} from '../../vimscript/expression/build';
16-
import { EvaluationContext } from '../../vimscript/expression/evaluate';
17+
import { EvaluationContext, toInt, toString } from '../../vimscript/expression/evaluate';
1718
import {
1819
envVariableParser,
1920
expressionParser,
@@ -27,7 +28,6 @@ import {
2728
Expression,
2829
OptionExpression,
2930
RegisterExpression,
30-
Value,
3131
VariableExpression,
3232
} from '../../vimscript/expression/types';
3333
import { displayValue } from '../../vimscript/expression/displayValue';
@@ -37,6 +37,11 @@ type Unpack = {
3737
type: 'unpack';
3838
names: string[];
3939
};
40+
type Index = {
41+
type: 'index';
42+
variable: VariableExpression;
43+
index: Expression;
44+
};
4045

4146
export type LetCommandOperation = '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '.=' | '..=';
4247
export type LetCommandVariable =
@@ -47,7 +52,7 @@ export type LetCommandVariable =
4752
export type LetCommandArgs =
4853
| {
4954
operation: LetCommandOperation;
50-
variable: LetCommandVariable | Unpack;
55+
variable: LetCommandVariable | Unpack | Index;
5156
expression: Expression;
5257
lock: boolean;
5358
}
@@ -81,8 +86,16 @@ const unpackParser: Parser<Unpack> = sepBy(varNameParser, string(',').trim(optWh
8186
names,
8287
}));
8388

89+
const indexParser: Parser<Index> = seq(
90+
variableParser,
91+
expressionParser.wrap(string('[').then(optWhitespace), optWhitespace.then(string(']'))),
92+
).map(([variable, index]) => ({
93+
type: 'index',
94+
variable,
95+
index,
96+
}));
97+
8498
export class LetCommand extends ExCommand {
85-
// TODO: Support indexing
8699
// TODO: Support slicing
87100
public static readonly argParser = (lock: boolean) =>
88101
alt<LetCommand>(
@@ -92,7 +105,7 @@ export class LetCommand extends ExCommand {
92105
// `:let {var} .= {expr}`
93106
whitespace.then(
94107
seq(
95-
alt<LetCommandVariable | Unpack>(letVarParser, unpackParser),
108+
alt<LetCommandVariable | Unpack | Index>(unpackParser, indexParser, letVarParser),
96109
operationParser.trim(optWhitespace),
97110
expressionParser,
98111
).map(
@@ -141,30 +154,34 @@ export class LetCommand extends ExCommand {
141154
}
142155
}
143156

144-
let value = context.evaluate(this.args.expression);
145-
if (variable.type === 'variable') {
157+
const value = context.evaluate(this.args.expression);
158+
const newValue = (_var: Expression) => {
146159
if (this.args.operation === '+=') {
147-
value = context.evaluate(add(variable, value));
160+
return context.evaluate(add(_var, value));
148161
} else if (this.args.operation === '-=') {
149-
value = context.evaluate(subtract(variable, value));
162+
return context.evaluate(subtract(_var, value));
150163
} else if (this.args.operation === '*=') {
151-
value = context.evaluate(multiply(variable, value));
164+
return context.evaluate(multiply(_var, value));
152165
} else if (this.args.operation === '/=') {
153-
value = context.evaluate(divide(variable, value));
166+
return context.evaluate(divide(_var, value));
154167
} else if (this.args.operation === '%=') {
155-
value = context.evaluate(modulo(variable, value));
168+
return context.evaluate(modulo(_var, value));
156169
} else if (this.args.operation === '.=') {
157-
value = context.evaluate(concat(variable, value));
170+
return context.evaluate(concat(_var, value));
158171
} else if (this.args.operation === '..=') {
159-
value = context.evaluate(concat(variable, value));
172+
return context.evaluate(concat(_var, value));
160173
}
161-
context.setVariable(variable, value, this.args.lock);
174+
return value;
175+
};
176+
177+
if (variable.type === 'variable') {
178+
context.setVariable(variable, newValue(variable), this.args.lock);
162179
} else if (variable.type === 'register') {
163180
// TODO
164181
} else if (variable.type === 'option') {
165182
// TODO
166183
} else if (variable.type === 'env_variable') {
167-
value = str(env[variable.name] ?? '');
184+
// TODO
168185
} else if (variable.type === 'unpack') {
169186
// TODO: Support :let [a, b; rest] = ["aval", "bval", 3, 4]
170187
if (value.type !== 'list') {
@@ -176,13 +193,33 @@ export class LetCommand extends ExCommand {
176193
if (variable.names.length > value.items.length) {
177194
throw VimError.fromCode(ErrorCode.MoreTargetsThanListItems);
178195
}
179-
for (let i = 0; i < variable.names.length; i++) {
180-
await new LetCommand({
181-
operation: this.args.operation,
182-
variable: { type: 'variable', namespace: undefined, name: variable.names[i] },
183-
expression: value.items[i],
184-
lock: this.args.lock,
185-
}).execute(vimState);
196+
for (const name of variable.names) {
197+
const item: VariableExpression = { type: 'variable', namespace: undefined, name };
198+
context.setVariable(item, newValue(item), this.args.lock);
199+
}
200+
} else if (variable.type === 'index') {
201+
const varValue = context.evaluate(variable.variable);
202+
if (varValue.type === 'list') {
203+
const idx = toInt(context.evaluate(variable.index));
204+
const newItem = newValue({
205+
type: 'index',
206+
expression: variable.variable,
207+
index: int(idx),
208+
});
209+
varValue.items[idx] = newItem;
210+
context.setVariable(variable.variable, varValue, this.args.lock);
211+
} else if (varValue.type === 'dict_val') {
212+
const key = toString(context.evaluate(variable.index));
213+
const newItem = newValue({
214+
type: 'entry',
215+
expression: variable.variable,
216+
entryName: key,
217+
});
218+
varValue.items.set(key, newItem);
219+
context.setVariable(variable.variable, varValue, this.args.lock);
220+
} else {
221+
// TODO: Support blobs
222+
throw VimError.fromCode(ErrorCode.CanOnlyIndexAListDictionaryOrBlob);
186223
}
187224
}
188225
}

src/error.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export enum ErrorCode {
4848
ArgumentOfSortMustBeAList = 686,
4949
LessTargetsThanListItems = 687,
5050
MoreTargetsThanListItems = 688,
51+
CanOnlyIndexAListDictionaryOrBlob = 689,
5152
CanOnlyCompareListWithList = 691,
5253
InvalidOperationForList = 692,
5354
CannotIndexAFuncref = 695,
@@ -132,6 +133,7 @@ export const ErrorMessage: IErrorMessage = {
132133
686: 'Argument of sort() must be a List',
133134
687: 'Less targets than List items',
134135
688: 'More targets than List items',
136+
689: 'Can only index a List, Dictionary or Blob',
135137
691: 'Can only compare List with List',
136138
692: 'Invalid operation for List',
137139
695: 'Cannot index a Funcref',

src/vimscript/expression/evaluate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
// ID of next lambda; incremented each time one is created
2424
let lambdaNumber = 1;
2525

26-
function toInt(value: Value): number {
26+
export function toInt(value: Value): number {
2727
switch (value.type) {
2828
case 'number':
2929
return value.value;

test/vimscript/exCommandParse.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,20 @@ suite('Ex command parsing', () => {
379379
}),
380380
);
381381

382+
exParseTest(
383+
':let x[7] = "foo"',
384+
new LetCommand({
385+
operation: '=',
386+
variable: {
387+
type: 'index',
388+
variable: { type: 'variable', namespace: undefined, name: 'x' },
389+
index: int(7),
390+
},
391+
expression: str('foo'),
392+
lock: false,
393+
}),
394+
);
395+
382396
exParseTest(
383397
':const foo = 5',
384398
new LetCommand({ operation: '=', variable: variable('foo'), expression: int(5), lock: true }),

0 commit comments

Comments
 (0)