Найти в Дзене
Блокнот математика

Рубрика "Секреты Вим". Скриптовый язык: Основы

Привет, коллеги. Вим содержит встроенный язык программирования, с которым полезно быть знакомым, хотя вот именно писать скрипты нужно редко. И если нужно, можно делать это на Питоне (о чем в другой раз), а если скомпилировать Вим с поддержкой Перл — то и на нем. Однако переменные и функции нужны часто, мы с ними уже встречались, да и некоторые полезные штуки можно делать малой кровью, так что полезно иметь представление. Да и понять чужие скрипты иногда бывает нужно.

Эта заметка касается основ языка и посвящена переменным, циклам, условиям, выражениям, логике, выводу сообщений. Это первая заметка из цикла "Скриптовый язык".

Хакер. "Just another Vim hacker".
Хакер. "Just another Vim hacker".

Можно вводить команды, снабдив их двоеточием, непосредственно из командной строки Вим, одну за другой. Можно записать скрипт в отдельный файл и выполнить его через :source. Можно занести скрипт прямо в .vimrc или в другой автовыполняемый файл, чтобы он выполнился при запуске, при открытии файла нужного типа или при наступлении иного события...

Начнем с вывода на экран: команды echo. После нее перечисляется все, что вы хотите вывести: переменные, текст в кавычках, числа. Есть вариант echon, который не ставит символ конца строки. Выводятся всегда десятичные числа, даже если на вход поданы шестнадцатеричные или восьмеричные. Как обычно, восьмеричные начинаются с нуля, а шестнадцатеричные — в 0x. Так можно легко сконвертировать числа:

echo 0x42 042 0x666-0666-666

Строковые константы могут быть в одинарных или в двойных кавычках. В первом случае все символы означают себя, кроме одинарной кавычки (чтобы ее вставить в строку, ее надо удвоить). В двойных можно использовать спецсимволы, которых много: \t (tab), \n (конец строки), \\ (сам \), \<ESC> и так далее. Можно вставить любой символ по коду (\x0f, \uffff, \U допускает до 8 цифр в 16-ричной системе).

Пример: echo "Your\ttextwidth=" &tw

Что, как вы думаете, выведет следующая команда? echo '\n' 42 "\n" 666

Выражения

Мы уже коснулись немного этой темы, выведя результат выражения 0x666-0666-666. Кроме констант (числовых и строковых) и переменных, в выражения могут входить переменные окружения ($HOME), опции Вим (&textwidth), содержимое регистра (@x). Опции можно различать глобальные (&g:option) и локальные (&l:option). Безымянный регистр тоже доступен (@" или @@).

Арифметика обычная, деление целых чисел целочисленное, есть символ % для взятия остатка от деления. Операции степени нет, но есть функция pow. Вещественные числа есть: echo (3.14+2.7)/42.0 работает предсказуемо. А вот комплексных нет и pow(-1, 0.5) дает -NaN.

Только помните, что деление целых есть деление нацело: 7/5 и 7.0/5.0 суть разные вещи. Второе можно и так записать: 7.0/5 или 7/5.0.

Для строк есть конкатенация через символ "точка". Что выведут команды:

echo 42 666
echo 42.666
echo 42 . 666
echo 42. 666
echo 42 .666
?

Подсказка: echo разделяет выводимые выражения пробелом, а вещественное число должно обладать и целой, и дробной частями.

Строки считаются списками символов и к ним применима нотация квадратных скобок: echo 'This is a text'[5:6]. О списках в следующий раз.

Логические операции тоже почти без сюрпризов. Истиной считается ненулевое число. Нуль, естественно, ложь. Строки преобразуются в число в логическом контексте, а если преобразовать не удалось совсем, то строка считается нулем, то есть ложной. Так что "true" — это ложное значение. А вот "2times" истинно, так как эта строка конвертируется в число 2. С другой стороны, строка "0.666" ложна, потому что конвертируется в нуль.

Преобразование строк в числа (и обратно) осуществляется в большинстве случаев, когда это уместно. Например, в арифметике: "6times" + "7times" даст 13. Исключения из этого правила я укажу.
Одно из исключений нам встретилось: вещественное число в строку не преобразуется, как и строка в вещественное число! Строка '6.66' превратится в число 6. А 4.2 . 'is 42' выдаст ошибку, так как число в строку преобразовано не будет, а конкатенация требует именно строку.

Логические связки || (ИЛИ), && (И), ! (НЕ) тоже обычные. Как и сравнения (==, !=, =, >= и т.п.). Работают и для чисел, и для строк, только строки сравниваются по байтам, что слабо связано с алфавитным порядком.

Например, 'ё' больше 'я', потому что 'ё' это d1 91, а 'я' это d1 8f. Добавим к этому то, что 'Я' это d0 af, то есть меньше обеих.

Для строк зато есть сравнение с регулярным выражением: =~ и !~. Первое истинно, если совпадение есть, второе — если нет. Выражение, если содержит спецсимволы, удобно ставить в одинарные кавычки. При сравнении используется опция ignorecase. Преодолеть ее можно, вставив в выражение ключ \c или \C, либо добавив символ ? (регистр не важен) или # (регистр важен) к оператору. Например, =~?, !=# , ==? и т.п.

Присваивание переменной или опции осуществляется конструкцией
let name = value.

Справа может стоять выражение.

Имеется си-подобное условное выражение L ? Y : N, где L — выражение, результат которого рассматривается как логическое значение, а Y и N — какие-то выражения. Результат зависит от истинности L.

Условия. Условный оператор никаких сюрпризов не таит. Скобки не нужны. Пример скажет сам за себя:

if &tw < 80
echo "<80"
elseif &tw < 100
echo "<100"
else
echo "big"
endif

Циклы также обычные:

let i=1
let s=0
while i<=10
let s+=i*i
let i+=1
endwhile
echo "Sum of " i-1 "squares is\t" $s

Обратите внимание, что счетчик цикла надо вручную увеличивать. Неудобно, зато можно делать с ним, что вздумается. Есть еще цикл for для обхода списков, обсуждение которого мы отложим до обсуждения самих списков.

Ну и обычные операторы break и continue тоже присутствуют: один прерывает выполнение цикла и выходит из него, второй прерывает итерацию и возвращается к началу.

Иногда удобно запускать вечный цикл и выходить из него через break. Тогда пишите while 1.

Переменные

Имена переменных следуют тем же правилам, что и везде: допустимы буквы, цифры и символ _, причем первым символом не должна быть цифра. Есть только такая тонкость: в именах можно использовать фигурные скобки, и то, что в них, будет вычислено и использовано как часть имени. Так можно создавать переменные для разных значений опций, например: my_{&background}_message: это будет my_dark_message или my_light_message, в зависимости от значения опции.

Важно, что переменные глобальны. Однако есть префиксы вроде s:name. Префиксы позволяют различать разные классы переменных и делать их локальными. Так, s:name локальна для скрипта, b:name локальна для буфера, t:name — локальна для вкладки, w:name локальна для окна, g:name глобальна (переменные в функциях локальны, поэтому глобальную надо объявлять так), v:name объявлены Вимом, a:name — аргументы функции, l:name локальна для функции.

Есть предопределенная переменная b:changetick, считающая изменения текущего буфера. Остальные системные переменные Вим имеют префикс v:. Например, v:version упомяну, с ней хорошо потренироваться.

Еще про присваивание let name = value. Как уже сказано, можно присваивать не только переменным, но и опциям, регистрам и переменным окружения (&tw, @x, $ENVVAR). Так можно вставить результат вычисления в текст:

:let @q=42*666
"qp

Есть сокращения let n+=v и let n-=v. А вот *= и /= нету. Для строк есть .=

Помним, что конвертация строк в целые числа и обратно происходит при необходимости (почти всегда). Так что '4'+'2' даст 6, а 4 . 2 даст '42'. А вот 4.2 это четыре целых две десятых.

Для конвертации строк в вещественные числа есть функция str2float(), для обратной конвертации printf(), и float2nr() для отсечения дробной части. Есть также round, floor, ceil для округлений. О них в другой раз.

Команда unlet позволяет удалить переменную. С восклицательным знаком — отсутствие переменной не считается ошибкой.

Переменные после завершения скрипта не удаляются и этим можно пользоваться. Например, считать вызовы скрипта. Или сохранять какие-то величины между вызовам.

Проверить существование переменной позволяет функция exists, только надо помнить, что ей нужно давать имя переменной в кавычках, а не значение: if exists('varname').

Просто let позволяет посмотреть, какие переменные у нас есть.

Итак, мы познакомились с основами языка. У нас есть все, что нужно, для создания простых программ. И у нас есть доступ к опциям Вим и, не забудем, мы можем выполнять любые :команды! А через :normal мы можем выполнять и команды нормального режима. То есть, мы уже можем кое-что вытворять. Правда, пока мы можем только выполнять статичные команды: подставить значение переменной не умеем.

Теперь научимся выполнять динамические команды. Как, например, сохранить буфер в файл, имя которого задано в переменной?

Для этого есть команда :execute. Например,

let b:file_name = 'some.file'
execute 'w ' b:file_name

Комбинируя execute и normal, можно выполнять и динамические команды нормального режима.

И есть функция eval(), которая выполняет код и возвращает значение.

Чего нам не хватает?

  1. Нам надо уметь создавать свои функции, вызывать их и уже имеющиеся,
  2. и знать, какие нам предлагает Вим.
  3. Нам нужны массивы, списки и словари и методы работы с ними.
  4. И хотелось бы знать о предопределенных функциях, в которых много интересного. В конце концов, нам же нужен доступ именно к Виму.

Обо всем этом — в другой раз! Совсем скоро.

Научно-популярные каналы на Дзене: путеводитель
Новости популярной науки12 марта 2022